C#异步方法的简介和常用所遇到的异步任务的解决办法

下属内容主要分为9个部分。
异步编程的概念,如何取消异步编程,异步任务如何超时,异步线程通讯,再同步方法中调用异步方法,ValueTask,在异步应用和取消长时间执行的同步任务,同时完成任务

1.异步编程的概念

1.异步编程要求不阻塞状态
2.线程适合长期运行,计算量大,线程创建销毁会较大影响效率
3.异步,适合密集操作,适合时间少的小任务,可以避免线程阻塞

1.1异步任务Task

1.Task是一个引用类型,包含正在运行,完成,结果,报错等任务状态
2.异步会借助线程池在其他线程上运行
3.获取结果后回到之前的状态,
4.Task包含有返回值,其中有,任务状态和结果
5.将方法标记asyn后,可以在方法中await
6.异步任务的不阻塞状态,是当进入线程时会自动开辟一个线程去执行
7.async void,在UI内容需要异步执行时,会使用async对事件进行形容。并且由于async void没有task方法,所以异常报错将不能在外部被捕获,所以建议仅在无返回值的事件中使用
8.异步方法中不能使用阻塞方法,会导致底层同步运行,例如(Thread.sleep(10)可以更换为await Task.Delay(20),进行暂停异步中的线程执行)。补充:在一个方法中有多个Task存在时,他会在方法对每个Task循序执行,如果被await形容时,则会等待对应方法执行结束。所以在async形容的方法中建议使用(await Task.Delay(20)作为线程等待操作)

1.2 Task的底层逻辑

1.async和await会将方法包装成一个状态机,await相当于检查点,通过对状态机进行切换状态实现

2.如何取消异步编程

2.1使用CancellationTokenSource

 static async Task Main(string[] args)
 {
     //设置cts的超时
     var cts = new CancellationTokenSource(3000);
     //获取cts的标识符
     var token=cts.Token;
     try
     {
         //模拟正在执行的任务
         await Task.WhenAll(Task.Delay(50000, token));
     }
     catch (Exception ex)
     {
         Console.WriteLine(ex.ToString());
     }
     finally 
     {
         //将cts进行dispose
         cts.Dispose();
     }   
     Console.ReadKey(); 
 }

2.2在大部分带有异步编程的.net库中,都支持传入Token

static async Task Main(string[] args)
{
    //设置cts的超时
    var cts = new CancellationTokenSource(3000);
    //获取cts的标识符
    var token=cts.Token;
    try
    {
        //模拟正在执行的任务
        await Task.WhenAll(Task.Delay(50000, token));
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
    finally 
    {
        //将cts进行dispose
        cts.Dispose();
    }   
    Console.ReadKey(); 
}

2.3可以使用带有Token的重载方法进行

static async Task Main(string[] args)
{
    //设置cts的超时
    var cts = new CancellationTokenSource(500);
    //获取cts的标识符
    var token=cts.Token;
    //注册token的善后工作    
    token.Register(() => { });
    Program program = new Program();
    try
    {
        Console.WriteLine("run");
        string a= await program.StrAsync(token);
        Console.WriteLine(a);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
    finally 
    {
        //将cts进行dispose
        cts.Dispose();
    }   
    Console.ReadKey(); 
}

async Task AsAsync(int delay, CancellationToken? cancellationToken = null)
{
    var token=cancellationToken??CancellationToken.None;
    //模拟方法中存在的一些变量
    string a = "1",b="b";
    //声明异常取消委托,在函数被跳出时,需要对函数中的一些参数进行内存清空
    token.Register(() => { a = "";b = ""; });
    //模拟同步方法中耗时的操作。
    //但是由于同步方法不能被操作,所以只能根据节点来校验是否处于cancel状态
    Thread.Sleep(100);
    //当校验到token被取消时,则抛出异常,可以被外部try catch捕获
    if(token.IsCancellationRequested)
        token.ThrowIfCancellationRequested();
    Thread.Sleep(100);
}

在绝大部分情况下,.net的库都带有异步编程的方法,名称后面通常以async结尾。
在任何cts结束后,需要进行dispose,cts属于引用类型,并且始终在内存中存在,所以非常容易造成内存泄漏。
通过委托事件进行善后工作,其中Token的善后工作可以多次被注册,但是注册顺序链表类型,先进后出,永远最先执行最后注册的委托事件

3. 异步任务如何超时

3.1使用扩展超时函数的形式

static async Task Main(string[] args)
{

    Program program = new Program();
    var cts = new CancellationTokenSource(10000);
    //获取cts的标识符
    var token=cts.Token;
    try
    {
        //设置超时时间
        int TimeOut = 500;
        var timeout = Task.Run(async () => { await program.tastAsync(token); }).Wait(TimeOut);
        if (!timeout) 
        { 
            cts.Cancel();
        }

    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
    finally
    {
        cts.Dispose();
    }
   
    Console.WriteLine("1");
    Console.ReadKey();
}
async Task tastAsync(CancellationToken? cancellationToken=null)
{

    try
    {
        var token = cancellationToken ?? CancellationToken.None;
        await Task.Delay(3000, token);
        Console.WriteLine("2");
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
    
}

4. 异步线程通讯队列

static async Task Main(string[] args)
{
    //创建线程通讯队列
    var channel = Channel.CreateUnbounded<string>();
    Program program = new Program();

    var sender = program.send(channel.Reader);
    var Writ = program.Wr(channel.Writer);
    await Writ;
    //触发生产完成
    channel.Writer.Complete();
    await sender;
    Console.WriteLine("go");
    Console.ReadKey();
}
/// <summary>
/// 出队
/// </summary>
/// <param name="reader"></param>
/// <returns></returns>
async Task send(ChannelReader<string> reader)
{
    //C#8.0以上版本可以使用
    //await foreach (var str in reader.ReadAllAsync())
    //{
    //    await Task.Delay(200);
    //    Console.WriteLine(str);
    //}
    
    try
    {
        //判断是否生产完成,并且读取完成
        while (!reader.Completion.IsCompleted)
        {
            string a = await reader.ReadAsync();
            Console.WriteLine(a);
            await Task.Delay(200);
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }

}
/// <summary>
/// 入队
/// </summary>
/// <param name="writer"></param>
/// <returns></returns>
async Task Wr(ChannelWriter<string> writer)
{
    for (int i = 0; i < 20; i++)
    {
        //写入数据
        await writer.WriteAsync(i.ToString());
        Console.WriteLine("fall" + i);
        await Task.Delay(100);
    }
}

5. ValueTask

绝大部分情况下都不推荐使用。只有在对内存资源要求非常严格时再去使用
由于valueTask是一个值类型,所以在部分内存开销上会有更多的优势
通常是带有返回值的,大量重复运行的内容,但是有必须使用异步来完成的函数,才推荐使用ValueTask
由于ValueTask的善后工作过去的繁琐,所以正常情况下不推荐使用

 static async Task Main(string[] args)
 {
     var res = await GetIntAsync(10);
     Console.WriteLine(res);
     Console.ReadKey();
 }
 public static async ValueTask<int> GetIntAsync(int id)
 {
     await Task.Delay(1000);
     return id*id;
 }

6. 在同步方法中调用异步方法

6.1使用阻塞的形式调用异步方法

static void Main(string[] args)
{
    Foo().GetAwaiter().GetResult();
}
static async Task Foo()
{
    await Task.Delay(1000);
}

6.2通过使用扩展方法

public class demo
{
    //用于记录函数是否被执行结束
    public bool ISLoadImage {  get; set; }=false;
    public demo() 
    {
        //e=>throw e弹出错误信息
        LoadImage().SafeFireAndForget(() => { ISLoadImage = true; },e=>throw e);
    }
   
    async Task LoadImage()
    {
        await Task.Delay(1000);
    }
}
public static class TaskExtenSions
{
    public static async void SafeFireAndForget(this Task task, Action? onCompleted = null, Action<Exception>? onError = null)
    {
        try
        {
            await task;
            onCompleted?.Invoke();
        }
        catch (Exception ex)
        {
            onError.Invoke(ex);
        }
    }
}

7. 异步任务实现同步机制和信号量

使用异步信号门,实际效果类似于(Lock)

//设置信号量的门的宽度为1
public static SemaphoreSlim semaphore = new SemaphoreSlim(1);
static async Task Main(string[] args)
{
    TimeSpan start = new TimeSpan(DateTime.Now.Ticks);  //获取当前时间的刻度数
    var tasks=new List<Task>();
    //创建10个异步任务
    for (int i = 0; i < 10; i++) 
    {
        tasks.Add(comAsync());
    }
    await Task.WhenAll(tasks);
    TimeSpan end = new TimeSpan(DateTime.Now.Ticks);    //获取当前时间的刻度数
    TimeSpan abs = end.Subtract(start).Duration();      //时间差的绝对值


    Console.WriteLine(abs.TotalMilliseconds);//输出double类型值ms为单位
    Console.ReadKey();
}
public static async Task comAsync()
{
    //开启门
    await semaphore.WaitAsync();
    await Task.Delay(200);
    //结束门
    semaphore.Release();

}

8.在异步应用和取消长时间运行的同步任务

namespace 在异步应用和取消长时间运行的同步任务
{
    internal class Program
    {
        static async Task Main(string[] args)
        {
            try
            {
                //创建超时
                var cts = new CancellationTokenSource(3000);
                //进入同步任务
                var task = new CancelableThreadTask(LoadImage,ErrorTaskDispose, TaskDispose);
                //等待同步任务并超时
                await task.RunAsync(cts.Token);
                cts.Dispose();
                Console.ReadKey();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
            Console.ReadKey();
        }
        /// <summary>
        /// 长时间的同步任务
        /// </summary>
        public static void LoadImage()
        {
            Thread.Sleep(5000);
            Console.WriteLine("go");
        }
        /// <summary>
        /// 定义同步任务的结束委托,通常用在再同步任务执行结束后,执行一些内容,或者清空内存
        /// </summary>
        public static void TaskDispose()
        {
            Console.WriteLine("Dispose");
        }
        /// <summary>
        /// 定义捕获信息,可以讲异常进行进行捕获,并执行后续操作,通常进行清空内存
        /// </summary>
        /// <param name="e"></param>
        public static void ErrorTaskDispose(Exception e )
        {
            Console.WriteLine("ErrorTaskDispose");
            Console.WriteLine(e.ToString() );
        }
    }
    public class CancelableThreadTask
    {
        private Thread? _thread;
        private bool ok;
        private readonly Action _action;
        private readonly Action<Exception> _onError;
        private readonly Action _onnCompleted;

        private int _isRun = 0;
        public CancelableThreadTask(Action action, Action<Exception> onError=null,Action? onnCompleted=null)
        {
            _action = action;
            _onError = onError;
            _onnCompleted = onnCompleted;
        }
        public Task RunAsync(CancellationToken token) 
        {
            //原子操作,防止在多线程访问是出现问题
            if (Interlocked.CompareExchange(ref _isRun,1,0)==1) 
            {
                throw new InvalidOperationException(" Task is alread running");
            }

            var _tcs = new TaskCompletionSource<bool>();
            _thread = new Thread(() =>
            {
                try
                {
                    _action();
                    _tcs.TrySetResult(ok);
                    _onnCompleted();
                }
                catch (Exception e)
                {
                    if (e is ThreadInterruptedException)
                        _tcs.TrySetCanceled();
                    else
                        _tcs.SetException(e);
                    //传出异常捕获信息
                    _onError?.Invoke(e);
                }
                finally { Interlocked.Exchange(ref _isRun, 0); }
            });
            token.Register(() => 
            {
                //超时时中断线程
                if (Interlocked.CompareExchange(ref _isRun, 0, 1) == 1)
                    _thread.Interrupt();
            });
            _thread.Start();
            return _tcs.Task;
        }
    }
}

9.多个异步任务同时完成

使用场景,当需要多个任务随意进行,但是当全部任务执行结束后,将全部任务同时进行下去。例如当我们需要在不同类和函数中去获取api或者访问文件时,并且需要当全部完成时返回一个状态。则就适合使用这种方式

namespace 多个异步任务同时完成
{
    /// <summary>
    /// 使用场景,当需要多个任务随意进行,但是当全部任务执行结束后,将全部任务同时进行下去
    /// </summary>
    internal class Program
    {
        //需要安装包Microsoft.VisualStudio.Threading
        public static AsyncBarrier AsyncBarrier = new AsyncBarrier(3);
        static async Task Main(string[] args)
        {
            
            Run1();
            await Task.Delay(500);
            Run2();
            await Task.Delay(5000);
            Run3();
            Console.WriteLine("finally");
            Console.ReadKey();
        }
        public static async Task Run1()
        {
            Console.WriteLine("run1");
            await Task.Delay(1000);
            await AsyncBarrier.SignalAndWait();
            Console.WriteLine("1");
        }
        public static async Task Run2()
        {
            Console.WriteLine("run2");
            await Task.Delay(1000);
            await AsyncBarrier.SignalAndWait();
            Console.WriteLine("2");
        }
        public static async Task Run3()
        {
            Console.WriteLine("run3");
            await Task.Delay(1000);
            await AsyncBarrier.SignalAndWait();
            Console.WriteLine("3");
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值