服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C# - C# async/await任务超时处理的实现

C# async/await任务超时处理的实现

2023-03-09 15:21熊思宇 C#

本文主要介绍了C# async/await任务超时处理的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

一、需求

在之前的帖子中,介绍了async / await 的用法,那么新的问题又来了,如果调用一个异步方法后,一直不给返回值结果怎么办呢?这就涉及到怎么取消任务了。添加一个任务后,如果固定时间内没用返回结果,那么就取消执行,并且在多个任务同时执行的时候,依然按顺序来执行,这就是本文章要实现的功能。

 

二、Task取消任务

先介绍一下 Task 结束任务的传统用法。在 C# 以前的2.0 等版本中,线程是可以强制终止的,到了后面,就不允许强制去结束线程的,后面微软就提供了一个CancellationTokenSource 相关的接口开源取消任务。于是我搜索了大量的帖子,一看全是各种抄袭,相互抄来抄去的,搞的我真的火冒三丈,完全是浪费时间,那么总结取消的方法如下:

namespace 取消任务3
{
  internal class Program
  {
      static CancellationTokenSource source = new CancellationTokenSource();

      static void Main(string[] args)
      {
          Task.Run(() =>
          {
              for (int i = 0; i < 10; i++)
              {
                  Thread.Sleep(100);
                  Console.WriteLine("oh my god");
                  source.Token.ThrowIfCancellationRequested();
              }
          }, source.Token);

          Thread.Sleep(2000);
          Console.WriteLine("取消任务");
          source.Cancel();

          Console.ReadKey();
      }
  }
}

运行:

C# async/await任务超时处理的实现

这个可以取消任务是吧,那下面换一个写法:

namespace 取消任务3
{
  internal class Program
  {
      static CancellationTokenSource source = new CancellationTokenSource();

      static void Main(string[] args)
      {
          Task.Run(() =>
          {
              Thread.Sleep(3000);
              Console.WriteLine("oh my god");
              source.Token.ThrowIfCancellationRequested();
          }, source.Token);

          Thread.Sleep(1000);
          Console.WriteLine("取消任务");
          source.Cancel();

          Console.ReadKey();
      }
  }
}

任务中等待三秒,我在等待一秒后取消任务,看看结果:

C# async/await任务超时处理的实现

这回就不管用了,任务明明取消了,但结果依然执行了。

根据他们写的例子,可以总结一点,就是要想取消任务,在任务中,必须加入 for 或者 while 循环,并且在下一轮循环中,执行到 source.Token.ThrowIfCancellationRequested() 这句才能取消任务。

换个写法,如果非得用 for 或者 while 循环这样的语法才能取消任务,我在 while 循环中加入一个判断,如果等于 true,直接跳出循环,这不也是中断了任务,所以说CancellationTokenSource 真的意义不大

namespace 取消任务4
{
  internal class Program
  {
      static void Main(string[] args)
      {
          bool isOut = false;

          var task1 = Task.Run(() =>
          {
              for (int i = 0; i < 100; i++)
              {
                  if (isOut) return;

                  Console.WriteLine("执行中" + i);
                  Thread.Sleep(500);
              }
          });

          Thread.Sleep(2000);
          Console.WriteLine("取消任务");
          isOut= true;

          Console.ReadKey();
      }
  }
}

运行:

C# async/await任务超时处理的实现

 

三、Task取消任务的回调

取消任务也是可以加入回调的,如下:

namespace 取消任务2
{
  internal class Program
  {
      static CancellationTokenSource source = new CancellationTokenSource();

      static void Main(string[] args)
      {
          var task1 = Task.Run(() =>
          {
              for (int i = 0; i < 100; i++)
              {
                  source.Token.ThrowIfCancellationRequested();
                  Console.WriteLine("执行中" + i);
                  Thread.Sleep(500);
              }
          }, source.Token);

          //在指定的毫秒数后取消task执行
          source.CancelAfter(2 * 1000);

          //取消任务后的回调
          source.Token.Register(() =>
          {
              //不延迟会获取不到正确的状态
              Thread.Sleep(50);
              Console.WriteLine("task1状态:" + task1.Status);
              Console.WriteLine("IsFaulted状态:" + task1.IsFaulted);//由于未处理的异常,任务已完成。
              Console.WriteLine("IsCompleted状态:" + task1.IsCompleted);//获取一个值,该值指示任务是否已完成。
          });

          Console.ReadKey();
      }
  }
}

运行:

C# async/await任务超时处理的实现

 

四、Task超时处理的实现

在上面的介绍中可以看到,Task取消任务传统的用法并不好用,必须在里面加上条件判断,如果满足条件就跳出for 或者 while 循环,达到方法执行完成的目的,而并不是真的终止了任务。

那么这么需求要如何去完成呢,微软官方也提供了一个叫 Task.WhenAny 接口,可以实现这个功能,下面就看看如何实现的。

新建一个基于 .Net6 的 Winform 项目,新建一个脚本Lib.cs

namespace Utils
{
  public static class Lib
  {

      public static async Task<bool> TryWithTimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout, Action<TResult>? successor = null)
      {
          //用于取消任务
          using CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource();

 
          //WhenAny 等待所有任务结束,这里加入了超时时间
          //ConfigureAwait 配置用来等待 任务1的警报,返回值可以获取到改任务
          Task? completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token)).ConfigureAwait(continueOnCapturedContext: false);


          //如果当前任务完成了,并且匹配
          if (completedTask == task)
          {
              //取消任务
              timeoutCancellationTokenSource.Cancel();

              //得到任务返回结果
              var result = await task.ConfigureAwait(continueOnCapturedContext: false);

              //执行回调
              if(successor != null) 
                  successor(result);

              return true;
          }
          else //任务超时
              return false;
      }
  }
}

界面如下,就几个按钮

C# async/await任务超时处理的实现

代码:

using Utils;

namespace 异步编程
{
  public partial class Form1 : Form
  {
      public Form1()
      {
          InitializeComponent();
      }


      private void button1_Click(object sender, EventArgs e)
      {
          Test1();
      }

      private void button2_Click(object sender, EventArgs e)
      {
          Test2();
      }

      private void Button_ClearConsole_Click(object sender, EventArgs e)
      {
          Console.Clear();
      }

      private async void Test1()
      {
          var task1 = Task.Run(() =>
          {
              Thread.Sleep(3000);
              return "task1";
          });
          bool res1 = await task1.TryWithTimeoutAfter(TimeSpan.FromSeconds(4), (string msg) =>
          {
              Console.WriteLine("-----------------------task1回调:" + msg);
          });

          string isTimeout1 = res1 == true ? "没超时" : "超时";
          Console.WriteLine("任务1:" + isTimeout1);


          var task2 = Task.Run(() =>
          {
              Thread.Sleep(3000);
              return "task2";
          });

          bool res2 = await task2.TryWithTimeoutAfter(TimeSpan.FromSeconds(4), (string msg) =>
          {
              Console.WriteLine("-----------------------task2回调:" + msg);
          });

          string isTimeout2 = res2 == true ? "没超时" : "超时";
          Console.WriteLine("任务2:" + isTimeout2);
      }

      private async void Test2()
      {
          var task3 = Task.Run(() =>
          {
              Thread.Sleep(3000);
              return "task3";
          });

          bool res3 = await task3.TryWithTimeoutAfter(TimeSpan.FromSeconds(4), (string msg) =>
          {
              Console.WriteLine("-----------------------task3回调:" + msg);
          });

          string isTimeou3 = res3 == true ? "没超时" : "超时";
          Console.WriteLine("任务2:" + isTimeou3);
      }
  }
}

按钮1和按钮2 方法里有三个异步方法,超时时间都是4秒,也就是说,如果方法在4秒之内没有返回值则为失败。

分别点击按钮1,按钮2

C# async/await任务超时处理的实现

在按钮1方法里有两个异步方法,异步方法1执行完成后,才能执行异步方法2,所以异步方法2要比异步方法3更慢一些。

下面就将三个异步方法的超时时间改为1秒,看看效果:

C# async/await任务超时处理的实现

返回超时,而且任务也没有执行,这样就实现了我们的想要的效果了。

 

五、Task.WhenAny 的异常

在一系列的测试中,我发现了 Task.WhenAny 这个接口在 .Net6 的控制台项目的异常之处,执行一次是正常的,如果在一个方法内同时执行多次,返回结果就开始乱了,在 Winform 项目中是没有这种事的,下面开始演示。

新建一个基于 .Net6 的控制台项目,将上面的 Lib.cs 代码复制到项目中来。

代码:

using Utils;

namespace 异步编程2
{
  internal class Program
  {
      static void Main(string[] args)
      {
          AwaitReturnValue();

          Console.ReadKey();
      }

      public static async void AwaitReturnValue()
      {
          var task1 = Task.Run(() =>
          {
              Thread.Sleep(3000);
              return "task1";
          });


          for (int i = 0; i < 10; i++)
          {
              bool res1 = await task1.TryWithTimeoutAfter(TimeSpan.FromSeconds(1), (string msg) =>
              {
                  Console.WriteLine("task1回调:" + msg);
              });

              string isTimeout = res1 == true ? "没超时" : "超时";
              Console.WriteLine(string.Format("结果{0}:{1}", i, isTimeout));
          }
      }
  }
}

运行:

C# async/await任务超时处理的实现

任务前两次是正确的,后面返回的基本全是错误的,原因我估计是 Task 任务内部等待时间是3秒,调用了前两次时间没有超过三秒,所以返回是正确的,后面超过3秒后,全当在超时范围内返回了。

 

六、其他的写法

超时取消任务的写法可以有多种,其实万变不离其宗,都是用 Task.WhenAny 方法实现的,代码我全部放一个类里面了,有兴趣的可以看看,有很多的高级语法,确实是值得学习的。

代码:

namespace 异步编程1
{
  public static class Lib1
  {
      public static async Task<TResult?> TimeoutAfter<TResult>(this Task<TResult> task, int timeout)
      {
          using (var cancelToken = new CancellationTokenSource())
          {
              Task completedTask = await Task.WhenAny(task, Task.Delay(timeout, cancelToken.Token));
              if (completedTask == task)
              {
                  cancelToken.Cancel();
                  return await task;
              }
              else
              {
                  // 超时处理
                  Console.WriteLine("超时了");
                  return default;
              }
          }
      }

      public static async Task<bool> OnTimeout<T>(T t, Action<T> action, int waitms) where T : Task
      {
          if (!(await Task.WhenAny(t, Task.Delay(waitms)) == t))
          {
              action(t);
              return true;
          }
          else
          {
              return false;
          }
      }

      public static async Task<bool> TryWithTimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout, Action<TResult> successor)
      {
          using var timeoutCancellationTokenSource = new CancellationTokenSource();
          var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token)).ConfigureAwait(continueOnCapturedContext: false);

          if (completedTask == task)
          {
              timeoutCancellationTokenSource.Cancel();

              // propagate exception rather than AggregateException, if calling task.Result.
              var result = await task.ConfigureAwait(continueOnCapturedContext: false);
              successor(result);
              return true;
          }
          else
              return false;
      }

      public static async Task<bool> BeforeTimeout(Task task, int millisecondsTimeout)
      {
          if (task.IsCompleted) return true;
          if (millisecondsTimeout == 0) return false;
          if (millisecondsTimeout == Timeout.Infinite)
          {
              await Task.WhenAll(task);
              return true;
          }

          var tcs = new TaskCompletionSource<object>();

          using (var timer = new Timer(state => ((TaskCompletionSource<object>)state).TrySetCanceled(), tcs, millisecondsTimeout, Timeout.Infinite))
          {
              return await Task.WhenAny(task, tcs.Task) == task;
          }
      }
  }
}

到此这篇关于C# async/await任务超时处理的实现的文章就介绍到这了,更多相关C# async/await任务超时内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://blog.csdn.net/qq_38693757/article/details/127935673

延伸 · 阅读

精彩推荐
  • C#C#自定义控件VS用户控件

    C#自定义控件VS用户控件

    这篇文章主要为大家详细介绍了C#中自定义控件VS用户控件的区别,介绍了开发自己的控件的几种方法、自定义控件与用户控件区别,具有一定的参考价值,...

    野狼谷11102021-12-16
  • C#C#连接SQL Server数据库的实例讲解

    C#连接SQL Server数据库的实例讲解

    在本篇文章里小编给大家整理了关于C#连接SQL Server数据库的实例内容,有需要的朋友们参考学习下。...

    人生、蜕变10582022-08-20
  • C#C#及WPF获取本机所有字体和颜色的方法

    C#及WPF获取本机所有字体和颜色的方法

    这篇文章主要介绍了C#及WPF获取本机所有字体和颜色的方法,实例分析了C#及WPF获取本机字体及颜色的相关技巧,非常简单实用,需要的朋友可以参考下...

    C#教程网5972021-10-27
  • C#C# 利用IRawPixels接口遍历栅格数据

    C# 利用IRawPixels接口遍历栅格数据

    本文主要介绍了利用IRawPixels接口遍历栅格数据。具有很好的参考价值,下面跟着小编一起来看下吧...

    杰桀9112021-12-23
  • C#C#获取任务栏显示进程的方法

    C#获取任务栏显示进程的方法

    这篇文章主要介绍了C#获取任务栏显示进程的方法,涉及C#针对进程操作的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...

    我心依旧7962021-10-22
  • C#带你复习c# 托管和非托管资源

    带你复习c# 托管和非托管资源

    这篇文章主要介绍了c# 托管和非托管资源的相关资料,文中讲解非常细致,帮助大家更好的理解和学习,感兴趣的朋友可以了解下...

    团队buff工具人5752022-09-26
  • C#c#实现windows远程桌面连接程序代码

    c#实现windows远程桌面连接程序代码

    本篇文章主要介绍了c#实现windows远程桌面连接程序代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    冰不化8832022-01-05
  • C#举例讲解C#编程中委托的实例化使用

    举例讲解C#编程中委托的实例化使用

    这篇文章主要介绍了C#编程中委托的实例化使用,包括委托的声明和多播委托的创建等内容,需要的朋友可以参考下...

    C#教程网5552021-11-11