最佳异步while方法

本文关键字:方法 while 异步 最佳 | 更新日期: 2023-09-27 18:29:55

我需要编写一些异步代码,这些代码本质上试图重复地与数据库对话并初始化数据库。第一次尝试往往会失败,因此需要重试。

在过去的日子里,我会使用类似于的模式

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中实现这个模式?

更新

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

最佳异步while方法

您可以像这样重构该片段:

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行中的部分将异步继续。以下也许是关于内部工作方式的最佳解释。问题是,通常在代码的某个时刻,您需要对异步任务的结果进行同步。例如:

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(使用不带async/await的TAP模式)。与第二个版本不同,第一个版本相当琐碎。因此,虽然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,Stephen Cleary的这篇文章是理解async/await线程概念的必读之作。

如果任务是异步的,您可以尝试使用:

    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.

只需提供另一个解决方案

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);
            }
        });
    }

您并不真正需要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
}