c#入门-异步方法

异步方法

如果一个操作会返回Task,那么用这个操作续接后续操作,也会得到Task。
也就是说Task具有传染性,最终拼凑出来的Task非常复杂。
使用异步方法,可以简化Task的拼凑。

async修饰

异步方法需要添加async修饰符。并且通常方法名以Async结尾。
在异步方法内可以使用await关键字
(因为以前它不是关键字,可能有人用这个作为变量名。
为了不破坏以前的代码,只有在有async修饰是await才是关键字)。

返回值

返回值必须是void或Task或Task的泛型版本。
异步方法内直接返回值,编译器会自动把他封装成Task。
例如方法返回值是Task<int>,那你只要返回int就行了。

await

await可以放置在一个Task的前面。这回等待它完成,并获取他的返回值。

async Task<string[]> ModAsync(string path, string url)
{
	Task<string[]> file = File.ReadAllLinesAsync(path);
	Task<string[]> http = File.ReadAllLinesAsync(url);//假设这是个网络访问

	var txt = await await Task.WhenAny(file, http);
	var taskArr = new Task<string>[txt.Length];
	for (int i = 0; i < taskArr.Length; i++)
	{
		taskArr[i] = File.ReadAllTextAsync(txt[i]);
	}
	return await Task.WhenAll(taskArr);
}

和直接调用Result或Wait不同的是,await会立刻视为方法完成然后继续运行这个方法后面的内容。

Console.WriteLine("主线程开始,即将调用异步方法");
var task = DelayAsync();
Console.WriteLine("异步方法调用完毕,等待异步方法输出值");
Console.WriteLine(task.Result);

async Task<int> DelayAsync()
{
	Console.WriteLine("异步方法开始执行,等待1秒钟");
	await Task.Delay(1000);
	Console.WriteLine("异步方法执行结束");
	return 12;
}
主线程开始,即将调用异步方法
异步方法开始执行,等待1秒钟
异步方法调用完毕,等待异步方法输出值
异步方法执行结束
12

可以看到,异步方法还没有结束的时候,主线程已经运作到了调用完毕处。
而await后面没有结束的内容,就会被打包成一个Task。在后台默默继续运行。
所以如果一个异步方法没有await的内容,就会弹出一个警告
在这里插入图片描述

异步迭代器

返回值为IAsyncEnumerable或IAsyncEnumerator的方法,可以同时使用await和yield语法。
在遍历异步迭代器时,可以在foreach前使用await。

Console.WriteLine("主线程开始,即将调用异步方法");
var task = DelayAsync();
Console.WriteLine("异步方法调用完毕,等待异步方法输出值");
Console.WriteLine(task.Result);
 
async Task<int> DelayAsync()
{
	int sum = 0;
	Console.WriteLine("异步方法开始执行,等待1秒钟");
	await foreach (var item in GetEnumerableAsync())
	{
		sum += item;
	}
	Console.WriteLine("异步方法执行结束");
	return sum;
}

async IAsyncEnumerable<int> GetEnumerableAsync()
{

	for (int i = 0; i < 10; i++)
	{
		await Task.Delay(100);
		yield return i;
	}
}

状态机

迭代器和异步方法都会把方法内容做成一个很复杂的东西。
迭代器执行到yield return时就会停止,直到下一次被调用。
异步方法执行到await就会挂起线程,当等待结束时重新排队线程。

但他们把内容做复杂的时候,把变量的作用域还原的很好。
使用using语句的时候,一旦出了作用域,就会执行释放方法。

Task<string> Get4399()
{
	using HttpClient client = new HttpClient();
	return client.GetStringAsync("www.4399.com");
}

async Task<string> Get4399Async()
{
	using HttpClient client = new HttpClient();
	return await client.GetStringAsync("www.4399.com");
}

第一个方法的网络访问会直接得到一个Task。把它直接返回是能和返回类型对应上的。
但实际上这样会报错。因为这个方法已经结束了,网络类就会关闭连接。
而还没有执行完成的任务,是需要保持连接才能访问到内容的。所以这个任务无法继续。
而第二个方法,会等到数据获取完成,这个方法才真正结束,网络类才释放。

即刻完成的任务

有时候,为了返回值能和接口对应上,或者重写基类的异步方法,
需要返回值是Task。而自己实际方法体内不需要等待。
又或者在异步方法内需要象征性的去await一个东西。
此时没有必要去创建一个需要通过线程池执行的任务,可以创建一个直接完成的任务。

已完成任务

CompletedTask静态属性是一个已经完成的任务。可以用来作为无返回值的任务来返回。

Task DoSomeAsync()
{
	return Task.CompletedTask;
}

从值创建完成任务

FromResult静态方法可以创建一个任务,并把他的输出设置为指定的值。

Task<int> GetIntAsync()
{
	return Task.FromResult(12);
}

屈服

Yield方法会创建一个可以await的东西,但不是Task。
它会让这个线程重新排队,等待一段非常小的时间。

async Task<int> DelayAsync()
{
	Console.WriteLine("异步方法开始执行,等待1秒钟");
	await Task.Yield();
	Console.WriteLine("异步方法执行结束");
	return 12;
}

如果使用Task.Delay(0)是达不到这样的效果的。
主线程不是每次遇到await都会视为方法结束,而是在需要等待await完成的时候才会结束。
把0作为延迟的参数,创建出来的就是一个即刻完成的任务。

而使用Task.Delay(1)也达不到这样的效果。
尽管你指定只延迟这么短暂的一点时间。但实际上操作系统无法管理得这么细致。
实际效果可能跟Task.Delay(15)差不多。

切换上下文

在窗体应用中(WPF,winfrom,unity),通常主线程会进行死循环操作,
为你刷新界面,这称为UI线程。为了线程安全,所有改变UI的操作只允许在UI线程中进行

通常,这类程序会在启动时配置线程环境(参阅SynchronizationContext),
让你的异步代码在await等待结束后,在合理时机,暂停UI线程的工作,让他接手await之后的操作。
因此,在窗体应用中使用异步方法,可以在方法内操控UI。

但如果不是使用await等待任务,而是调用了Wait或Result,会导致死锁
(你在控制台程序无法复现这个场景,因为控制台是系统调用的窗体,不是你程序的一部分)。

因为异步方法里的东西没有执行完成,他们需要继续执行才能完成。
而你使用Wait进行等待,主线程就会啥也不干。那他就干不了异步方法里的后续操作。
而后续操作没完成,主线程就得继续等。

Task的ConfigureAwait方法可以为当前的任务改变配置,让他可以通过别的线程来接手。
这样可以避免调用Wait造成的死锁。

async Task<int> Sum()
{
	return await Task.Run(() =>
	{
		int sum = 0;
		for (int i = 0; i < 100; i++)
		{
			sum += i;
		}
		return sum;
	}).ConfigureAwait(false);
}

在第一个await的任务后面加上ConfigureAwait(false),会告诉线程池让别的线程来接手。
ConfigureAwait(true)是默认行为,不需要调用。调用了也只是尽量让同一线程接手,不能保证必然。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值