`
- 浏览:
251613 次
- 性别:
- 来自:
北京
-
.NET(C#):await返回Task的async方法
众所周知,async方法只可以返回void,Task和Task<T>。
对于返回void的async方法,它并不是awaitable,所以其他方法不能用await方法来调用它,而返回Task的async方法则可以。
那么当async方法返回Task后,接着await,那被await的Task是一个什么概念?是async方法中第一个被await的Task?不,它代表目标async方法的全部执行,其中包括被await分割的连接Task,但是不包括非await造成的多线程执行。
如下代码,在doo是一个返回Task的async方法,然后在另一个方法test中await调用doo,然后在Main方法中调用test(由于Main方法不允许加async,所以需要另外加一个async方法来使用await)
static void Main(string[] args)
{
test();
log("Main:调用test后");
Thread.Sleep(Timeout.Infinite);
}
//Main方法不允许加async,所以我们用这个方法使用await
static async void test()
{
log("test: await之前");
await doo();
log("test: await之后");
}
//返回Task的async方法
static async Task doo()
{
log("doo: Task结果:" + await Task.Run(() => { Thread.Sleep(1000); log("Task"); return 1; }));
log("doo: Task结果:" + await Task.Run(() => { Thread.Sleep(1000); log("Task"); return 2; }));
log("doo: Task结果:" + await Task.Run(() => { Thread.Sleep(1000); log("Task"); return 3; }));
Thread.Sleep(1000);
Console.WriteLine("doo中在Task外的Thread.Sleep执行完毕");
}
//输出方法:显示当前线程的ManagedThreadId
static void log(string msg)
{
Console.WriteLine("{0}: {1}", Thread.CurrentThread.ManagedThreadId, msg);
}
上面代码会输出:
1: test: await之前
1: Main:调用test后
3: Task
3: doo: Task结果:1
4: Task
4: doo: Task结果:2
3: Task
3: doo: Task结果:3
doo中在Task外的Thread.Sleep执行完毕
3: test: await之后
前两句简单,调用test方法,await后的内容会被加在目标Task的后面,然后test马上返回,于是输出“Main:调用test后”,同时他们都是在主线程中执行的,所以ManagedThreadId都是1。
接着后面就是另一个Task的执行(当然在另一个线程,也是test方法中await的目标Task)。这个所谓的Task就是doo方法的全部执行。所以doo中三个顺序执行的Task(通过await一个一个连接)依次执行,所以Task输出结果1,2,3。第一个Task的ManagedThreadId是3,第二个是4,第三个又是3,原因是Task的内部执行使用了CLR的线程池,所以线程得到了重复利用。
接着doo方法还没有完,最后一个await造成doo方法后面的代码在这个await针对的Task执行后继续执行,于是输出:doo中Task外的Thread.Sleep执行完毕。
最后当doo彻底执行完test的await才结束,所以最后一行输出:test:await之后。
上面我说过:被await的async方法返回的Task代表“目标async方法的全部执行,其中包括被await分割的连接Task,但是不包括非await造成的多线程执行”。
所以如果把返回Task的async方法(也就是上例中的doo方法)改成这样:
//返回Task的async方法
static async Task doo()
{
log("doo: Task结果:" + await Task.Run(() => { Thread.Sleep(1000); log("Task"); return 1; }));
log("doo: Task结果:" + await Task.Run(() => { Thread.Sleep(1000); log("Task"); return 2; }));
log("doo: Task结果:" + await Task.Run(() => { Thread.Sleep(1000); log("Task"); return 3; }));
//不使用await:线程池多线程
ThreadPool.QueueUserWorkItem(_ =>
{
Thread.Sleep(1000);
Console.WriteLine("ThreadPool.QueueUserWorkItem");
});
//不使用await:Task多线程
Task.Run(() =>
{
Thread.Sleep(1000);
Console.WriteLine("Task.Run");
});
}
我们加入了不用await的多线程执行,分别使用ThreadPool和Task,整个程序会输出这样的结果:
1: test: await之前
1: Main:调用test后
3: Task
3: doo: Task结果:1
4: Task
4: doo: Task结果:2
3: Task
3: doo: Task结果:3
3: test: await之后
Task.Run
ThreadPool.QueueUserWorkItem
不使用await的多线程完全脱离了test方法中await的Task,是运行在test的await之后的。
另外Visual Studio会对Task.Run代码做如下警告:
提示:Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the ‘await’ operator to the result of the call.
就是说,如果不加await,当前方法会继续执行直到结束,不用管他,因为我们现在就是在做在async方法中不用await的测试,呵呵。
或许你会问,为什么要用这样的方式去await另一个async方法返回的Task呢?我们一直在讨论返回Task的async方法,我认为看一个返回Task<T>的async方法可以更好地解释这个问题。
下面我们把上面的代码改成相似的返回Task<int>的async方法执行,那么doo方法返回Task<T>,他把自己方法内3个awaited Task的结果统一相加,最后返回结果并作为自己返回的Task的结果。然后在test方法中输出doo返回的结果。
完整代码:
static void Main(string[] args)
{
test();
log("Main:调用test后");
Thread.Sleep(Timeout.Infinite);
}
//Main方法不允许加async,所以我们用这个方法使用await
static async void test()
{
log("test: await之前");
Console.WriteLine("doo结果:{0}", await doo());
log("test: await之后");
}
//返回Task的async方法
static async Task<int> doo()
{
var res1 = await Task.Run(() => { Thread.Sleep(1000); log("awaited Task1执行"); return
var res2 = await Task.Run(() => { Thread.Sleep(1000); log("awaited Task2执行"); return
var res3 = await Task.Run(() => { Thread.Sleep(1000); log("awaited Task3执行"); return
//不使用await:线程池多线程
ThreadPool.QueueUserWorkItem(_ =>
{
Thread.Sleep(1000);
Console.WriteLine("ThreadPool.QueueUserWorkItem");
});
//不使用await:Task多线程
Task.Run(() =>
{
Thread.Sleep(1000);
Console.WriteLine("Task.Run");
});
return res1 + res2 + res3;
}
//输出方法:显示当前线程的ManagedThreadId
static void log(string msg)
{
Console.WriteLine("{0}: {1}", Thread.CurrentThread.ManagedThreadId, msg);
}
先看结果:
1: test: await之前
1: Main:调用test后
3: awaited Task1执行
4: awaited Task2执行
4: awaited Task3执行
doo结果:6
4: test: await之后
ThreadPool.QueueUserWorkItem
Task.Run
和上一个返回Task的例子一样,当在test方法中await doo方法返回的Task,doo内awaited Task都被先等了,而没有awaited的线程都并没有被等,这是为什么呢(也就是上面留下的那个问题)?下面用这个返回Task<int>的例子解释一下:
在test中await doo返回的Task,那么此时我们需要他的结果,而他的结果是需要自己方法内所包含的其他awaited结果,可以理解成被等的子结果。所以自己的结果需要其他的结果,那么等这个结果必须需要等那些被依赖的结果也出来。所以test方法await doo方法的结果会同样等待所有doo内的await,不会管其他doo内非await的多线程执行(当然从技术角度讲,也是不可能的,因为async/await可以这样全靠的是编译器)。
:D
分享到:
Global site tag (gtag.js) - Google Analytics
相关推荐
在.NET Framework 4.0的环境中,开发人员经常会遇到需要执行异步操作的需求,而`.NET Framework 4.5`引入的`async/await`关键字为异步编程提供了极大的便利。`async/await`模式使得代码更加简洁、易读,避免了复杂的...
在C# 5.0及更高版本中,`Task`, `Await` 和 `Async` 关键字是异步编程的核心工具,它们极大地简化了多线程和非阻塞操作的处理,尤其对于UI应用程序如WinForm,能确保用户体验的流畅性。本示例通过一个简单的WinForm...
C# 5.0 引入 async/await 关键字,旨在简化异步编程模型,抛去语法糖就是 Net4.0 的 Task + 状态机。其实在处理异步编程使用 Task 还是挺简单的,不过既然推出了新的语法糖,难免会尝试一下,然而在使用中却没想象中...
- 异步方法通常以`async`修饰,内部使用`await`等待异步操作完成。 5. **ASP.NET Web 应用程序组件**: - 页面生命周期:理解ASP.NET页面从加载到呈现的整个生命周期,包括初始化、加载、验证、呈现等阶段。 - ...
在C#编程中,`async`和`await`是异步编程的关键关键字,它们使得开发者能够编写出非阻塞式的代码,从而提高了应用程序的性能和响应性。`async`和`await`通常与任务并行库(TPL)一起使用,为处理耗时的操作如I/O操作...
举一个简单的例子,下面是一个使用async和await关键字的异步方法,该方法执行Web请求并返回响应长度: ```csharp private async Task<long> AccessWebAsync() { using (var webRequest = WebRequest.Create("***")...
- 异步编程模型:Task和async/await关键字 7. **第七章:.NET的反射和元数据** - 反射的概念和用途 - 使用Type、MethodInfo、ConstructorInfo等类进行反射操作 - 应用场景:动态代理、配置文件解析 8. **第八...
需要注意的是,使用`async`修饰的方法的返回类型必须是`void`、`Task`或`Task<TResult>`。 2. **`await`**关键字只能出现在由`async`修饰的方法内部,并且用于等待一个异步操作完成。当`await`表达式遇到时,当前...
在C#编程中,`await` 和 `async` 关键字是异步编程的核心,它们引入了一种更优雅的方式来处理耗时的操作,如I/O密集型任务(如数据库查询、文件读写)或网络请求,避免阻塞主线程,提高应用程序的响应性和效率。...
`async`关键字用于标记一个方法为异步方法,它返回一个`Task`或`Task<T>`对象,表示异步操作的状态。`await`关键字用于挂起异步方法的执行,直到等待的任务完成。当`await`后的任务抛出异常时,这个异常会在调用链中...
3. **多线程/异步编程**:C#的Thread或Task类,配合async/await关键字,可以实现并发处理,提高下载速度。 综上所述,这个项目展示了如何在.NET C#环境中实现断点续传,涉及到客户端的WinForm应用设计、文件分块...
1. **异步方法**:C#中的异步方法通过`async`关键字标记,返回一个`Task`或`Task<T>`对象。这个方法体内部可以使用`await`关键字来暂停执行,直到某个异步操作完成。 2. **await 关键字**:`await`关键字用于等待一...
- Task和async/await关键字:理解异步编程模型,如何避免阻塞主线程。 - 回调函数和事件驱动的异步模式与Task-based异步模式的比较。 7. **垃圾回收(Garbage Collection, GC)**: - .NET内存管理:理解如何...
确实,没有异步的多线程是单调的、乏味的,async和await是出现在C#5.0之后,它的出现给了异步并行变成带来了很大的方便。异步编程涉及到的东西还是比较多,本篇还是先介绍下async和await的原理及简单实现。 了解...
在.NET Framework 4.5及更高版本和C# 5.0及以上语法中,async和await关键词引入了任务异步编程模型(Task Asynchronous Programming, TAP),使得开发者可以更方便地编写非阻塞的异步代码。 首先,`async`关键字...
1. **Task类**:C#的`System.Threading.Tasks.Task`类是.NET Framework提供的基础异步编程模型。Task代表一个工作单元,它可以是CPU密集型或I/O密集型的任务,提供了启动、等待和查询任务状态的能力。 2. **异步...
C#提供了Task、Thread或者async/await关键字来实现这一功能,这样可以在后台执行下载,同时保持UI的响应性。 文件I/O操作是下载器的另一个关键部分。在C#中,我们使用System.IO命名空间下的类,如FileStream和...
public async Task<string> TranslateText(string text, string sourceLanguage, string targetLanguage, string apiKey) { using var client = new HttpClient(); client.DefaultRequestHeaders.Add(...
参照asp.net 上的教程《如何:使用 Task.WhenAll 扩展异步演练 (C#)》,使用了async/await以后,感觉怪怪的。 使用nginx作为服务器,对每个连接限制速度50K。 然同时开启了10个任务进行分段下载(HTTP 1.1支持下载...
- ADO.NET:学习如何使用DataSet、DataTable和DataAdapter进行数据库操作。 - Entity Framework:理解ORM(对象关系映射)工具,简化数据库操作。 10. **单元测试与调试** - NUnit和MSTest:学习如何编写和执行...