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

时间:2012-04-06 11:37:02

标签: c# asynchronous async-ctp

我正在尝试异步CTP,即允许使用异步方法而不需要编写Begin / End方法的版本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项目中,我有一个textBox,用于查看结果和一个执行异步方法的按钮。

在async方法中,我使用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时,这是使用async的正确方法吗?

2 个答案:

答案 0 :(得分:4)

  

如果我没有错,Task.Run会创建一个新线程来执行该方法。

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

  

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

在UI应用程序的上下文中,async的要点是能够在和异步操作完成后在UI线程上轻松执行某些代码。

所以,如果你让method01Async“等待”,那就是让它返回Task

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

然后你可以从btnAsync01_Click方法等待它,如果你把它做成'async:

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

这样,方法的最后一行只有在Taskmethod01Async完成执行后才会执行。它将在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开始完成。


作为旁注,您无需在async中使用method01Async。直接返回Task(或者,如果你想保留它void - 返回),将同样如下:

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

答案 1 :(得分: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(),它会在退出后跳回那里,你的“在ethod01Async:之后”文本日志会显示十秒延迟,就像你在method01Async方法中的“exit method01Async”登录一样。