了解C#中的异步/等待


75

我开始学习C#5.0中的异步/等待,但我一点也不了解。我不明白如何将其用于并行性。我尝试了以下非常基本的程序:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Task task1 = Task1();
            Task task2 = Task2();

            Task.WaitAll(task1, task2);

            Debug.WriteLine("Finished main method");
        }

        public static async Task Task1()
        {
            await new Task(() => Thread.Sleep(TimeSpan.FromSeconds(5)));
            Debug.WriteLine("Finished Task1");
        }

        public static async Task Task2()
        {
            await new Task(() => Thread.Sleep(TimeSpan.FromSeconds(10)));
            Debug.WriteLine("Finished Task2");
        }

    }
}

该程序只是阻止调用,Task.WaitAll()并且永远不会结束,但是我不明白为什么。我敢肯定,我只是缺少一些简单的东西,或者只是没有正确的思维模式,而且没有任何博客或MSDN文章对您有所帮助。


5
取而代之的是await new Task....使用await Task.Delay(...);
David Heffernan

Answers:


68

我建议您从/的介绍asyncawait开始,并跟进TAP上的MSDN官方文档

正如我在介绍性博客文章中提到的那样,有几个Task成员是TPL的保留对象,没有使用纯async代码。new TaskTask.Start应替换为Task.Run(或TaskFactory.StartNew)。同样,Thread.Sleep应替换为Task.Delay

最后,我建议您不要使用Task.WaitAll; 您的控制台应用程序应该只是Wait在一个单一Task其用途Task.WhenAll。经过所有这些更改,您的代码将如下所示:

class Program
{
    static void Main(string[] args)
    {
        MainAsync().Wait();
    }

    public static async Task MainAsync()
    {
        Task task1 = Task1();
        Task task2 = Task2();

        await Task.WhenAll(task1, task2);

        Debug.WriteLine("Finished main method");
    }

    public static async Task Task1()
    {
        await Task.Delay(5000);
        Debug.WriteLine("Finished Task1");
    }

    public static async Task Task2()
    {
        await Task.Delay(10000);
        Debug.WriteLine("Finished Task2");
    }
}

感谢您的回应,斯蒂芬。您能否给我TL; DR为什么要等待Task.WhenAll(...)比Task.WaitAll()更可取?此外,您能否为我提供任何文档的链接,这些文档解释了WPF和Windows Runtime之类的API如何知道如何异步执行事件处理程序?
亚历克斯·马歇尔

1
阻止任务可能导致死锁(链接到我的博客)。在您的特定情况下,这是很好的,因为ConsoleMain方法是“请勿阻止”准则的例外。但是我更喜欢将特殊代码(使用Wait)从任何逻辑中分离出来,然后移至MainAsync。如果/当您将此代码复制/粘贴到GUI或ASP.NET应用程序中时,死锁的可能性将大大降低。
史蒂芬·克利西

2
异步事件处理程序使用current SynchronizationContext。WPF(我相信WinRT)不必做任何特别的事情;它们的存在SynchronizationContext足以使async事件处理程序正常运行。我在介绍性文章中解释了上下文捕获/恢复,并且还有一篇MSDN文章SynchronizationContext如果您觉得有趣的话会进行详细介绍。
Stephen Cleary

15

了解C#任务,异步并等待

C#任务

任务类是异步任务包装器。Thread.Sleep(1000)可以使线程停止运行1秒钟。虽然Task.Delay(1000)不会停止当前工作。看到代码:

public static void Main(string[] args){
    TaskTest();
}
private static void TaskTest(){
     Task.Delay(5000);
     System.Console.WriteLine("task done");
}

运行时,“任务已完成”将立即显示。因此,我可以假定Task中的每个方法都应该是异步的。如果我用Task.Run(()=> TaskTest())替换TaskTest(),直到我追加Console.ReadLine(),完成的任务才会显示。在运行方法之后。

在内部,Task类表示状态机中的线程状态。状态机中的每个状态都有几个状态,例如开始,延迟,取消和停止。

异步等待

现在,您可能想知道所有Task是否都是异步的,Task.Delay的目的是什么?接下来,让我们通过使用async和await来真正延迟运行线程

public static void Main(string[] args){
     TaskTest();
     System.Console.WriteLine("main thread is not blocked");
     Console.ReadLine();
}
private static async void TaskTest(){
     await Task.Delay(5000);
     System.Console.WriteLine("task done");
}

异步告诉调用者,我是一个异步方法,请不要等待我。在TaskTest()内部等待,等待异步任务。现在,运行后,程序将等待5秒钟以显示任务完成文本。

取消任务

由于Task是状态机,因此必须有一种在任务运行时取消任务的方法。

static CancellationTokenSource tokenSource = new CancellationTokenSource();
public static void Main(string[] args){
    TaskTest();
    System.Console.WriteLine("main thread is not blocked");
    var input=Console.ReadLine();
    if(input=="stop"){
          tokenSource.Cancel();
          System.Console.WriteLine("task stopped");
     }
     Console.ReadLine();
}
private static async void TaskTest(){
     try{
          await Task.Delay(5000,tokenSource.Token);
     }catch(TaskCanceledException e){
          //cancel task will throw out a exception, just catch it, do nothing.
     }
     System.Console.WriteLine("task done");
}

现在,在程序运行时,您可以输入“停止”以取消“延迟”任务。


12

您的任务永远不会完成,因为它们永远不会开始运行。

我将Task.Factory.StartNew创建一个任务并启动它。

public static async Task Task1()
{
  await Task.Factory.StartNew(() => Thread.Sleep(TimeSpan.FromSeconds(5)));
  Debug.WriteLine("Finished Task1");
}

public static async Task Task2()
{
  await Task.Factory.StartNew(() => Thread.Sleep(TimeSpan.FromSeconds(10)));
  Debug.WriteLine("Finished Task2");
}

附带说明一下,如果您实际上只是想在异步方法中暂停,则无需阻塞整个线程,只需使用 Task.Delay

public static async Task Task1()
{
  await Task.Delay(TimeSpan.FromSeconds(5));
  Debug.WriteLine("Finished Task1");
}

public static async Task Task2()
{
  await Task.Delay(TimeSpan.FromSeconds(10));
  Debug.WriteLine("Finished Task2");
}

1
为什么要创建新任务?
David Heffernan

1
@DavidHeffernan还没有机会将其添加到我的答案中,但是我认为Alex可能脑子里有些复杂。
MerickOWA 2013年

@MerickOWA是的,您是对的,我的想法确实比较复杂。我想模拟在IO上阻塞并花费很长时间的线程,并且这些线程不仅仅局限于CPU。
亚历克斯·马歇尔

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.