当方法返回void时,它和任务(task)是相同的吗?

8

我正在尝试使用async CTP,即版本4.5,它允许使用异步方法而无需编写Begin/End方法。

我的第一次尝试是执行一个返回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,这是必需的,因为该方法是异步的,并且使用TaskEx.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时,这是使用async的正确方式吗?

顺便提一下,Visual Studio 11 Beta现已发布,其中包含了Async CTP的功能,并进行了一些改进和错误修复。 - svick
在这种情况下,使用异步方法的好处是什么?如果我在方法中使用任务,并且该任务使用新线程,那么使用直接的任务(而不使用异步方法)会得到相同的行为。或许当方法返回一个值时,使用异步方法更有意义,对于void方法,直接使用任务会更好? - Álvaro García
我强烈建议所有的async方法都返回TaskTask<TResult>,除非它们是事件处理程序并且必须返回void。这样可以在需要时组合它们,并且错误处理也更加清晰(如果一个async void方法在WPF上下文中引发异常,则会直接发送到UI消息循环,因此async void方法并不真正是“fire and forget”)。 - Stephen Cleary
此外,仅使用Task.Run在线程池上运行代码。您可以使用Task.Delay进行异步等待,而不是排队阻塞的Sleep - Stephen Cleary
2个回答

4
如果我没错的话,Task.Run创建一个新线程以执行该方法。
并不完全是这样。Task.Run()将在与UI线程不同的线程上运行代码(至少使用默认的TaskScheduler)。但在大多数情况下,它实际上不会创建一个新线程,而是从线程池中重用现有线程。
那么,如果使用Task创建一个新线程,我可以得到想要的结果,即不阻塞主线程,为什么要使用异步方法呢?
在UI应用程序的上下文中,async的重点是能够在异步操作完成后轻松地在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)));
}

我相信你会同意这看起来更加凌乱,可读性更差。

此外,如果async方法访问一些共享变量,我必须小心并发,对吗?

是的,你说得对。

事实上,我使用相同的代码,没有使用async和await,结果是一样的,主程序不会阻塞,所有工作都按照我的预期进行。

结果肯定不是我认为你的代码应该执行的。在 btnAsync01_Click 的最后一行,将执行“after method01Async”,但它不会等待在该方法中启动的 Task 完成。


顺便提一句,在 method01Async 中无需使用 async。直接返回 Task(或者不返回,如果你想保持其返回类型为 void),效果是一样的:

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

当我说Task会创建一个新线程时,其实是在简化,我知道Task使用了线程池中的现有线程,如果没有可用的线程,那么任务必须等待。在我的情况下,我不想在主方法(此例中为单击事件)中等待,因为次要方法(method01Async)只是做一些不需要通知主方法的事情。例如,发送电子邮件给某个人。我只想发送,但并不关心或在主应用程序中通知操作是否完成。 - Álvaro García
我所想的是,在无返回值方法中,或许不需要使用async,而建议在有返回值方法中使用async。 - Álvaro García
如果操作完成后您不想执行任何操作,那么确实没有理由使用 await。尽管在发送电子邮件代码中使用它可以更有效地使用线程池。 - svick

1

无论哪种情况下,您实际上都没有使用异步,因为您没有等待原始调用。以下是您应该执行的操作:

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()),它将在退出后跳回那里,您的“after method01Async:”文本日志应显示十秒延迟,就像您在method01Async方法中的“exit method01Async”日志一样。


这段代码无法编译。如果你想在其中使用await,你需要将btnAsync01_Click改为async - svick
是的,需要将点击事件设为异步。但是通过这种方式,我有同样的疑问,如果在method01Async方法中使用任务(task),那么我正在使用一个新线程,因此我看不到使用异步的好处。这让我想到,如果异步方法返回结果更有意义,对于void方法,直接使用一个使用任务的线程可能更好。 - Álvaro García
@svick 是对的。我实际上没有编译过这个代码,所以是根据记忆在说。编译器会很快告诉你,你不能从未标记为 async 的方法中调用 await。已编辑以反映出这一点。感谢 svick。 - Chris Benard
@Daimroc,使用async/await的原因是你希望在返回结果后再执行某些操作。如果不等待Task返回结果,那么你的UpdateTxtLog("after ethod01Async: " + System.DateTime.Now);将会立即执行。而通过我的示例,它只会在Task完成后才会执行,并且仍然在另一个线程上运行,不会阻塞你的UI线程。 - Chris Benard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接