C#中对异步方法及异步lambda表达式

这篇文章的目的并不是系统地介绍C#中的awaitasync关键字,而是针对我遇到的一些问题进行记录。

背景

await / async

C#中可以用async标识方法,表示这个方法是异步的。异步方法的返回值必须是voidTask或者Task<T>。例如:

public static async Task<int> Method(int i)
{
    await Task.Delay(1000);
    return i;
}
  • 1
  • 2
  • 3
  • 4
  • 5

async修饰的lambda表达式

我们可以用async修饰lambda表达式,标识这个表达式为异步。

Func<Task<HttpResponseMessage>> lambda = async () =>
{
    using (var httpClient = new HttpClient())
    {
        var response = await httpClient.GetAsync("https://www.bing.com/");
        return response;
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

可以看到,用async修饰以后,返回值也变为Task<HttpResponseMessage>

async标记只是针对方法内部,外部只关心返回值类型。

异步方法返回的时间

异步方法在遇到await关键字之后就会返回。例如下面的代码:

private static Stopwatch stopwatch;

static void Main(string[] args)
{
    stopwatch = Stopwatch.StartNew();
    Task task = MethodAsync();
    Console.WriteLine($"Main: {stopwatch.ElapsedMilliseconds}");
}

public static async Task MethodAsync()
{
    Console.WriteLine("Enter method.");
    Task.Delay(500).Wait();
    Console.WriteLine($"Method will return: {stopwatch.ElapsedMilliseconds}");
    await Task.Delay(1000);
    Console.WriteLine($"End of method: {stopwatch.ElapsedMilliseconds}");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

MethodAsync()方法会在遇到await时返回。于是就有了如下输出:

Enter method.
Method will return: 542
Main: 544
  • 1
  • 2
  • 3

Task类的一些方法

Task.Run(Action action)

我们可以用Task.Run(Action action)来启动一个任务。

static void Main(string[] args)
{
    stopwatch = Stopwatch.StartNew();
    var task = Task.Run(MethodAsync);
    task.Wait();
    Console.WriteLine($"Main: {stopwatch.ElapsedMilliseconds}");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

其中MethodAsync就是上面定义的方法。

可以推测,MethodAsync执行到第一个await就会返回。Wait()方法也会执行完毕。整段程序执行500多毫秒就应该结束。

让我们运行一下。

Enter method.
Method will return: 544
End of method: 1546
Main: 1547
  • 1
  • 2
  • 3
  • 4

奇怪,MethodAsync返回不久,整个程序就应该结束才是,为什么等待了一秒多,直到MethodAsync全部执行完?

Task.Run(Func<Task> function)

原来,Task.Run那里实际调用的是Task.Run(Func<Task> function)这个重载。这个重载返回的Task对象,我们在调用其Wait()成员方法时会等待function返回的Task对象执行结束。我认为应该是为了方便这种情况。

await Task.Run(async () =>
{
    using (var httpClient = new HttpClient())
    {
        var response = await httpClient.GetAsync("https://www.bing.com/");
        return response;
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在这段代码中,正是由于调用的是Task.Run(Func<Task> function)这个重载,才保证了请求完成后才会继续执行后面的代码。

System.Threading.Tasks.Parallel类的情况

System.Threading.Tasks.Parallel类提供了非常方便的并发执行循环的方法。

Parallel.For(0, 10, i => Console.WriteLine(i));
  • 1

输出

0
2
6
4
5
8
9
7
3
1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

再试试

Parallel.For(0, 10, async i =>
{
    await Task.Delay(1000);
    Console.WriteLine(i);
});
  • 1
  • 2
  • 3
  • 4
  • 5

输出

  • 1

是的,输出没有任何内容。

原因是,ParallelTask不一样,它并不会等待返回的Task,而是在方法返回后就结束。

所以,如果想确保异步方法完全完成,必须改为同步。

Parallel.For(0, 10, i =>
{
    Task.Delay(1000).Wait();
    Console.WriteLine(i);
});
  • 1
  • 2
  • 3
  • 4
  • 5

输出

8
4
6
2
0
1
7
5
3
9
原文地址:https://www.cnblogs.com/Leo_wl/p/14140207.html