24

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

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

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

更新

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

4

4 回答 4

37

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

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(使用不带的TAP 模式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, Stephen Cleary 的这篇文章async/await是理解线程概念的必读文章。

于 2013-09-03T08:46:45.493 回答
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

于 2013-09-03T08:51:45.073 回答
2

只需提供另一种解决方案

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);
            }
        });
    }
于 2016-05-06T08:03:11.167 回答
0

您实际上并不需要WaitItForWork方法,只需等待数据库初始化任务:

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

async Task InitializeDatabase()
{
    // Perform database initialization here
}

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

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
}
于 2013-12-01T09:34:11.177 回答