【问题标题】:Best asynchronous while method最佳异步while方法
【发布时间】:2013-09-03 08:37:14
【问题描述】:

我需要编写一些异步代码,主要是尝试重复与数据库通信和初始化数据库。第一次尝试通常会失败,因此需要重试。

在过去,我会使用类似于以下的模式:

void WaitForItToWork()
{
    bool succeeded = false;
    while (!succeeded)
    {
        // do work
        succeeded = outcome; // if it worked, mark as succeeded, else retry
        Threading.Thread.Sleep(1000); // arbitrary sleep
    }
}

我意识到最近对 .NET 进行了很多关于异步模式的更改,所以我的问题真的是这是最好的使用方法,还是值得探索 async 的东西,如果是,我该如何实现async中的这种模式?

更新

澄清一下,我想异步生成这项工作,以便生成它的方法不必等待它完成,因为它将在服务的构造函数中生成,因此构造函数必须立即返回。

【问题讨论】:

    标签: c# .net multithreading async-await


    【解决方案1】:

    你可以像这样重构那个片段:

    async Task<bool> WaitForItToWork()
    {
        bool succeeded = false;
        while (!succeeded)
        {
            // do work
            succeeded = outcome; // if it worked, make as succeeded, else retry
            await Task.Delay(1000); // arbitrary delay
        }
        return succeeded;
    }
    

    显然,它能给您带来的唯一好处是更有效地使用线程池,因为它并不总是需要整个线程来实现延迟。

    根据您获得outcome 的方式,使用async/await 可能有更有效的方法来完成这项工作。通常你可能有类似GetOutcomeAsync() 的东西,它会以自然的方式异步调用Web 服务、数据库或套接字,所以你只需使用var outcome = await GetOutcomeAsync()

    重要的是要考虑到WaitForItToWork 将被编译器拆分为多个部分,await 行中的部分将异步继续。 Here's 可能是关于它是如何在内部完成的最好的解释。问题是,通常在您的代码的某个时刻,您需要同步异步任务的结果。例如:

    private void Form1_Load(object sender, EventArgs e)
    {
        Task<bool> task = WaitForItToWork();
        task.ContinueWith(_ => {
            MessageBox.Show("WaitForItToWork done:" + task.Result.toString()); // true or false
        }, TaskScheduler.FromCurrentSynchronizationContext());
    }
    

    您可以简单地这样做:

    private async void Form1_Load(object sender, EventArgs e)
    {
        bool result = await WaitForItToWork();
        MessageBox.Show("WaitForItToWork done:" + result.toString()); // true or false
    }
    

    然而,这将使Form1_Load 也成为异步方法。

    [更新]

    下面是我试图说明 async/await 在这种情况下的实际作用。 我创建了相同逻辑的两个版本,WaitForItToWorkAsync(使用async/await)和WaitForItToWorkAsyncTap(使用TAP pattern 而不使用async/await)。与第二个不同,第一个版本非常简单。因此,虽然async/await 在很大程度上是编译器的语法糖,但它使异步代码更容易编写和理解。

    // fake outcome() method for testing
    bool outcome() { return new Random().Next(0, 99) > 50; }
    
    // with async/await
    async Task<bool> WaitForItToWorkAsync()
    {
        var succeeded = false;
        while (!succeeded)
        {
            succeeded = outcome(); // if it worked, make as succeeded, else retry
            await Task.Delay(1000);
        }
        return succeeded;
    }
    
    // without async/await
    Task<bool> WaitForItToWorkAsyncTap()
    {
        var context = TaskScheduler.FromCurrentSynchronizationContext();
        var tcs = new TaskCompletionSource<bool>();
        var succeeded = false;
        Action closure = null;
    
        closure = delegate
        {
            succeeded = outcome(); // if it worked, make as succeeded, else retry
            Task.Delay(1000).ContinueWith(delegate
            {
                if (succeeded)
                    tcs.SetResult(succeeded);
                else
                    closure();
            }, context);
        };
    
        // start the task logic synchronously
        // it could end synchronously too! (e.g, if we used 'Task.Delay(0)')
        closure();
    
        return tcs.Task;
    }
    
    // start both tasks and handle the completion of each asynchronously
    private void StartWaitForItToWork()
    {
        WaitForItToWorkAsync().ContinueWith((t) =>
        {
            MessageBox.Show("WaitForItToWorkAsync complete: " + t.Result.ToString());
        }, TaskScheduler.FromCurrentSynchronizationContext());
    
        WaitForItToWorkAsyncTap().ContinueWith((t) =>
        {
            MessageBox.Show("WaitForItToWorkAsyncTap complete: " + t.Result.ToString());
        }, TaskScheduler.FromCurrentSynchronizationContext());
    }
    
    // await for each tasks (StartWaitForItToWorkAsync itself is async)
    private async Task StartWaitForItToWorkAsync()
    {
        bool result = await WaitForItToWorkAsync();
        MessageBox.Show("WaitForItToWorkAsync complete: " + result.ToString());
    
        result = await WaitForItToWorkAsyncTap();
        MessageBox.Show("WaitForItToWorkAsyncTap complete: " + result.ToString());
    }
    

    关于线程的几句话。这里没有显式创建额外的线程。在内部,Task.Delay() 实现可能使用池线程(我怀疑他们使用Timer Queues),但在这个特定示例(WinForms 应用程序)中,await 之后的延续将发生在同一个 UI 线程上。在其他执行环境(例如控制台应用程序)中,它可能会在不同的线程上继续。 IMO, this article Stephen Cleary 是理解async/await 线程概念的必读书籍。

    【讨论】:

    • 我可以通过 this.WaitForItToWork(); 来调用它吗?异步库会为我处理线程吗?
    • 您可以将其称为await this.WaitForItToWork(),并且必须重构整个调用链以支持此...我将详细说明我的答案以包含更多信息。
    • @Chris:你必须记住使用“await”关键字。经验法则:始终“等待”必须与“异步”功能相结合。所以,你应该做类似 await WaitForItToWork();
    • 我不想等待它,我想产生这个任务并继续我正在做的事情
    • 我刚刚更新了我的答案,希望现在更有意义。你实际上并没有等待(没有什么比阻止Sleep)。 await Task.Delay() 的最佳类比可能是计时器事件。 async/await 只是编译器提供的语法糖。在某种程度上它类似于node.js,以防你熟悉它(尽管 Node 的 javascript 缺乏语法便利——一切都是通过闭包完成的——C# 中的委托)。
    【解决方案2】:

    如果任务是异步的,你可以试试:

        async Task WaitForItToWork()
        {
            await Task.Run(() =>
            {
                bool succeeded = false;
                while (!succeeded)
                {
                    // do work
                    succeeded = outcome; // if it worked, make as succeeded, else retry
                    System.Threading.Thread.Sleep(1000); // arbitrary sleep
                }
            });
        }
    

    http://msdn.microsoft.com/en-us/library/hh195051.aspx

    【讨论】:

    • 这正是我想要避免的答案。也许,我自己错过了什么:]
    • @Noseratio 当await被命中时,异步方法将立即返回。 c# 编译器为您完成所有线程同步。 msdn.microsoft.com/en-us/library/vstudio/hh156528.aspx 重要的是; "await 表达式不会阻塞它正在执行的线程。相反,它会导致编译器注册 async 方法的其余部分作为等待任务的延续"
    • 我说错了。放松。即使事后纠正或增加清晰度也没有害处。我进行了编辑以避免其他读者的混淆。我没有编辑让你看起来很愚蠢。这将违反 DRY 原则。 /sickburn
    • 是的,也许在这种情况下,最好的解决方案是计时器,它会尝试每个间隔并在连接时禁用它。
    • 这是一个已知的反模式 (blogs.msdn.com/b/pfxteam/archive/2012/04/13/10293638.aspx)。我建议接受 Noseratio 的回答,因为我不希望未来的访问者认为这是最佳解决方案。
    【解决方案3】:

    只需提供另一种解决方案

    public static void WaitForCondition(Func<bool> predict)
        {
            Task.Delay(TimeSpan.FromMilliseconds(1000)).ContinueWith(_ =>
            {
                var result = predict();
                // the condition result is false, and we need to wait again.
                if (result == false)
                {
                    WaitForCondition(predict);
                }
            });
        }
    

    【讨论】:

      【解决方案4】:

      你真的不需要WaitItForWork方法,只是等待数据库初始化任务:

      async Task Run()
      {
          await InitializeDatabase();
          // Do what you need after database is initialized
      }
      
      async Task InitializeDatabase()
      {
          // Perform database initialization here
      }
      

      如果您有多个调用 WaitForItToWork 的代码,那么您需要将数据库初始化包装到 Task 并在所有工作人员中等待它,例如:

      readonly Task _initializeDatabaseTask = InitializeDatabase();
      
      async Task Worker1()
      {
          await _initializeDatabaseTask;
          // Do what you need after database is initialized
      }
      
      async Task Worker2()
      {
          await _initializeDatabaseTask;
          // Do what you need after database is initialized
      }
      
      static async Task InitializeDatabase()
      {
          // Initialize your database here
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-06-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多