当方法返回 void 时,与任务相同

本文关键字:任务 方法 返回 void | 更新日期: 2023-09-27 18:30:27

我正在尝试异步CTP,即允许使用异步方法而无需编写开始/结束方法的versión 4.5。

我的第一个探测是执行返回 void 的异步方法。我看到一些示例并执行以下操作:

private void btnAsync01_Click(object sender, RoutedEventArgs e)
{
    UpdateTxtLog("click button: " + System.DateTime.Now);
    method01Async();
    UpdateTxtLog("after ethod01Async: " + System.DateTime.Now);
}
private async void method01Async()
{
    await TaskEx.Run(() =>
    {
        UpdateTxtLog("Enter method01Async: " + System.DateTime.Now);
        Thread.Sleep(10000);
        UpdateTxtLog("exit method01Async: " + System.DateTime.Now);
    });
}

在我的 WPF 项目中,我有一个文本框,可以在其中查看结果,还有一个执行异步方法的按钮。

在异步方法中,我使用 await,这是需要的,因为该方法是异步的,并且 TasEx.Run 创建一个执行代码的新线程。

我的疑问在于这一点。在我看到的几个关于如何创建返回 void 的异步方法的例子中,请使用这种方式,即 Task.Run 或 TaskEx.Run。

如果我没记错的话,Task.Run 会在其中创建一个新线程来执行该方法。那为什么要使用异步方法,如果使用 Task,创建一个新线程,我得到了我想要的,而不是阻塞主线程?

另外,如果异步方法访问某些共享变量,我必须小心并发性,对吗?所以我不知道使用异步方法的好处,至少在这种情况下是这样。

实际上,我在没有异步和没有等待的情况下使用相同的代码,结果是相同的,主程序没有阻塞,并且一切都按我的预期工作。方法是这样的:

private void method01Async()
{
    TaskEx.Run(() =>
    {
        UpdateTxtLog("Enter method01Async: " + System.DateTime.Now);
        Thread.Sleep(10000);
        UpdateTxtLog("Exit method01Async: " + System.DateTime.Now);
    });
}

我的问题是,这是在方法返回 void 时使用异步的正确方法吗?

当方法返回 void 时,与任务相同

如果我没记错的话,Task.Run 会在其中创建一个新线程来执行该方法。

不完全是。 Task.Run()将在与 UI 线程不同的线程上运行代码(至少使用默认TaskScheduler )。但在大多数情况下,它实际上不会创建新线程,它会重用ThreadPool中的现有线程。

那为什么要使用异步方法,如果使用 Task,创建一个新线程,我得到了我想要的,而不是阻塞主线程?

async的要点是,在

UI应用程序的上下文中,能够在异步操作完成后轻松地在UI线程上执行一些代码。

因此,如果您将method01Async设置为"可等待",即使其返回Task

private async Task method01Async()
{
    await Task.Run(/* whatever */);
}

然后,您可以从btnAsync01_Click方法等待它,如果您将其设置为"异步:

private async void btnAsync01_Click(object sender, RoutedEventArgs e)
{
    UpdateTxtLog("click button: " + System.DateTime.Now);
    await method01Async();
    UpdateTxtLog("after method01Async: " + System.DateTime.Now);
}

这样,方法的最后一行将仅在 method01Async 中的Task完成执行后执行。它将在 UI 线程上执行。

在 .Net 4.0 中,您可以使用 ContinueWith()Dispatcher.Invoke() 实现类似的效果:

private void btnAsync01_Click(object sender, RoutedEventArgs e)
{
    UpdateTxtLog("click button: " + System.DateTime.Now);
    method01Async().ContinueWith(() =>
        Dispatcher.Invoke(
            new Action(() =>
                UpdateTxtLog("after method01Async: " + System.DateTime.Now)));
}

我相信你会同意这更混乱,可读性更差。

另外,如果异步方法访问某些共享变量,我必须小心并发性,对吗?

是的,你是对的。

实际上,我在没有异步和没有等待的情况下使用相同的代码,结果是相同的,主程序没有阻塞,并且一切都按我的预期工作。

结果肯定不是我认为你的代码应该做的。btnAsync01_Click的最后一行 将"在method01Async之后"执行,但它不会等到该方法中开始Task完成。


作为旁注,无需在您的method01Async中使用async。直接返回Task(或不返回,如果您想将其保留void -returning),将正常工作:

private Task method01Async()
{
    return Task.Run(/* whatever */);
}

在这两种情况下,您都没有真正使用异步,因为您没有等待原始调用。以下是您应该如何操作:

private async void btnAsync01_Click(object sender, RoutedEventArgs e)
{
    UpdateTxtLog("click button: " + System.DateTime.Now);
    await method01Async();
    UpdateTxtLog("after ethod01Async: " + System.DateTime.Now);
}
private async Task method01Async()
{
    return await TaskEx.Run(() =>
    {
        UpdateTxtLog("Enter method01Async: " + System.DateTime.Now);
        Thread.Sleep(10000);
        UpdateTxtLog("exit method01Async: " + System.DateTime.Now);
    });
}

一旦你把它改成这个(在你的按钮点击事件中await method01Async()的重要部分,它会在退出后跳回那里,你的"ethod01Async之后:"文本日志应该显示十秒的延迟,就像你的"退出方法01Async"日志在method01Async方法中一样。