【C#】【语法】c#中的异步操作 task与async/await

Task

Task 类表示一个异步操作。这个操作可以通过 Task.Run 方法、TaskFactory.StartNew 方法,或直接通过 new TaskTask.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 则需要额外的处理。

使用 await 比直接访问 Result 属性更符合异步编程的理念,因为它保持了应用的响应性,并且在语法上更加清晰和易于维护。await 使得异步编程模式更加接近传统的同步编程模式,简化了代码的可读性和异常处理。在实际应用中,推荐使用 await 来处理异步任务,尤其是在 UI 相关的应用程序中,以避免界面的冻结和延迟。

 

async方法

await 关键字只能在由 async 关键字修饰的方法中使用。

  • 声明:当你在方法的返回类型前添加 async 关键字时,你声明了一个异步方法。这个关键字启用了该方法中 await 表达式的使用,并表明该方法是异步的,通常会涉及到耗时操作的异步处理。
  • 返回类型async 方法可以有三种返回类型:
    • Task:用于没有返回值的异步操作。
    • Task<T>:用于有返回值的异步操作,其中 T 是返回值的类型。
    • void:不推荐使用,通常只用于事件处理器中。因为它不支持等待该方法的完成,也不能捕获其中的异常。

 

(注意:在 C# 中,如果你声明了一个返回 Taskasync 方法,你实际上不需要在方法中显式地使用 return 语句来返回一个 Task 对象。这是因为 async 关键字允许编译器为你处理返回 Task 的细节。

当你使用 async 关键字声明方法时,编译器会生成额外的代码,这些代码包括自动创建和返回一个 Task 对象。并确保该 Task 在方法执行完毕时正确地完成。这个过程是自动的,不需要你手动编写任何 return 语句。

如果方法正常完成,生成的 Task 会自动标记为已完成;如果方法中抛出异常,生成的 Task 会捕获这个异常,并在 Task 上反映出相应的异常状态。

async Task<T> 方法:对于返回具体结果的异步方法(返回 Task<T>),你需要使用 return 语句来提供一个类型为 T 的结果。编译器需要这个返回值来完成返回的 Task<T>

 

asyncawait 关键字使得异步代码更易于阅读和维护。标记为 async 的方法会返回一个 TaskTask<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 可以继续执行查询是否取消

 

posted @ 2024-06-11 15:33  ban_boi  阅读(26)  评论(0编辑  收藏  举报