【C#】【语法】c#中的异步操作 task与async/await
Task
Task
类表示一个异步操作。这个操作可以通过 Task.Run
方法、TaskFactory.StartNew
方法,或直接通过 new Task
和 Task.Start
来启动。
拿Task.Run
举例
Task task = Task.Run(() => { // 执行一些长时间运行的操作 });
Task.Run()
是一个用来执行异步操作的方法。它启动一个新的任务(Task),这个任务在后台线程上执行指定的代码块。这样,它可以使得CPU密集型或者长时间运行的任务不会阻塞主线程。
await Task.Run(() => { });
如果在不需要异步操作的执行结果的情况下,可以使用await Task.Run(() => { });
Task.Run()
自身并不涉及阻塞;它仅仅是启动了一个任务。这时,主线程(UI 线程)将在启动任务后立即继续执行后续的代码。这种情况下,虽然 UI 不会因为后台任务而阻塞,但如果主线程依赖于这个任务的结果来更新 UI 或进行其他操作,可能会遇到问题(如访问未完成计算的数据,导致错误或不一致的状态)。
如果需要在任务完成后执行一些操作(如更新 UI),应该使用 await
来确保这些操作在任务完成后正确执行。
举例说明,考虑以下代码:
// 假设这是一个按钮点击事件处理器
private void buttonStart_Click(object sender, EventArgs e)
{
Task task = Task.Run(() =>
{
// 执行一些长时间运行的操作
LongRunningOperation();
});
// 这里的代码会在 LongRunningOperation 启动后立即执行
UpdateUI("任务已启动,正在执行中...");
}
在这个例子中:
LongRunningOperation()
在后台线程中执行,不会阻塞 UI 线程。UpdateUI("任务已启动,正在执行中...");
被立即执行,因为主线程没有等待后台任务完成。
如果你需要在后台任务完成后进行操作(如再次更新 UI),你应该使用 await
确保操作在任务完成后执行:
private async void buttonStart_Click(object sender, EventArgs e)
{
await Task.Run(() =>
{
// 执行一些长时间运行的操作
LongRunningOperation();
});
// 任务完成后执行
UpdateUI("任务已完成!");
}
使用 Task<TResult>
类来表示返回结果的异步操作
可以使用 Task<TResult>
类来表示能返回结果的异步操作
Task<int> task = Task.Run(() => { return 1 + 1; });
要等待 Task
完成并获取结果,你可以使用 Result
属性,但这会阻塞当前线程:
int result = task.Result;
当你调用 task.Result
,如果 Task
尚未完成,当前线程将会阻塞,等待 Task
完成。这种行为在某些情况下(尤其是在 UI 线程中)是不可取的,因为它会冻结应用程序的响应,直到任务完成。
使用 await
关键字的改进:
使用 await
关键字可以更优雅地处理异步任务的结果,同时避免阻塞主线程。如果我们将第二段代码改为使用 await
,看起来会是这样:
int result = await task;
这样改写后的代码具有以下特点:
- 非阻塞行为:使用
await
会暂停当前方法的执行直到Task
完成,但它不会阻塞线程。在此期间,线程可以执行其他工作,如响应 UI 事件。 - 异常处理:使用
await
还可以更简单地处理任务中可能抛出的异常。如果Task
执行过程中出现异常,使用await
可以直接在try...catch
结构中捕获这些异常,而使用Result
则需要额外的处理。
async方法
await
关键字只能在由 async
关键字修饰的方法中使用。
- 声明:当你在方法的返回类型前添加
async
关键字时,你声明了一个异步方法。这个关键字启用了该方法中await
表达式的使用,并表明该方法是异步的,通常会涉及到耗时操作的异步处理。 - 返回类型:
async
方法可以有三种返回类型:Task
:用于没有返回值的异步操作。Task<T>
:用于有返回值的异步操作,其中T
是返回值的类型。void
:不推荐使用,通常只用于事件处理器中。因为它不支持等待该方法的完成,也不能捕获其中的异常。
(注意:在 C# 中,如果你声明了一个返回 Task
的 async
方法,你实际上不需要在方法中显式地使用 return
语句来返回一个 Task
对象。这是因为 async
关键字允许编译器为你处理返回 Task
的细节。
当你使用 async
关键字声明方法时,编译器会生成额外的代码,这些代码包括自动创建和返回一个 Task
对象。并确保该 Task
在方法执行完毕时正确地完成。这个过程是自动的,不需要你手动编写任何 return
语句。
如果方法正常完成,生成的 Task
会自动标记为已完成;如果方法中抛出异常,生成的 Task
会捕获这个异常,并在 Task
上反映出相应的异常状态。
async Task<T>
方法:对于返回具体结果的异步方法(返回 Task<T>
),你需要使用 return
语句来提供一个类型为 T
的结果。编译器需要这个返回值来完成返回的 Task<T>
)
async
和 await
关键字使得异步代码更易于阅读和维护。标记为 async
的方法会返回一个 Task
或 Task<T>
对象,你可以在该方法中使用 await
关键字来等待异步操作完成。
public async Task DoSomethingAsync() { // 异步执行某个方法,并等待其完成 await SomeMethodAsync(); // 上面的异步操作完成后,继续执行 }
await
会非阻塞地等待 Task
完成:它会释放当前线程,使其可用于其他操作,直到等待的 Task
完成。这样,你就可以在 UI 线程中安全地等待长时间运行的操作,而不会冻结用户界面
示例
首先在事件处理方法(定义为async才能调用await类型方法)中,调用异步方法await SendHexFramesAsync(cts_saveHex.Token)
//定义了一个 CancellationTokenSource 类型的变量 cts_saveHex。这个类用于生成一个或多个 CancellationToken 对象,这些对象可以传递给可以被取消的异步方法。 CancellationTokenSource cts_saveHex; //存储Flash按钮点击事件 private async void btn_saveFlash_Click(object sender, EventArgs e) { if (ComDevice.IsOpen) { //创建了一个新的 CancellationTokenSource 实例,并将其赋值给 cts_saveHex 变量。
就可以使用 cts_saveHex.Token 获取一个 CancellationToken,并传递给可取消的异步方法。 cts_saveHex = new CancellationTokenSource(); try { //执行 SendHexFramesAsync 异步方法,并传递 cts_saveHex.Token 作为参数。
这样,如果在 SendHexFramesAsync 方法内部检查了这个 token,并在其被取消时抛出了一个异常,你就可以在外部捕获这个异常并进行相应的处理。 await SendHexFramesAsync(cts_saveHex.Token); } catch (OperationCanceledException) { ButtonAction("open"); // 处理取消操作后的清理工作 if (language == "Chinese") listBox_Information.Items.Add("【" + DateTime.Now.ToString("HH:mm:ss:fff") + "】:已结束存储Hex任务"); else listBox_Information.Items.Add("[" + DateTime.Now.ToString("HH:mm:ss:fff") + "]:ended storing Hex task"); } //Timer_save.Enabled = true; } }
SendHexFramesAsync方法如下,有一个循环,在每次发送报文之后,非阻塞地等待:
//存储——异步发送hex private AutoResetEvent autoResetEvent = new AutoResetEvent(false); public async Task SendHexFramesAsync(CancellationToken cancellationToken) { for (int i = 0; i < blockInfo.DataSegments.Count; i++) { byte[] sendData2 = null; sendData2 = strToHexByte(realData1.Trim()); SendData(sendData2);//发每帧数据 //3 块首帧的计时器 timer_timeout.Interval = 10000; timer_timeout.Enabled = true; timer_timeout.Start(); // 1等待接收到回应 await Task.Run(() => autoResetEvent.WaitOne());//2检查是否请求了取消操作。如果有抛出一个OperationCanceledException异常 cancellationToken.ThrowIfCancellationRequested(); } }
接收数据方法里,如果接收成功:
autoResetEvent.Set(); // 设置信号,以便 SendFramesAsync 可以继续发送下一帧
如果失败或超时:
cts_saveHex.Cancel(); autoResetEvent.Set(); // 设置信号,以便 SendFramesAsync 可以继续执行查询是否取消