【深入浅出C# async/await】自定义Task

part1 - 【深入浅出C# async/await】编译篇
part2 - 【深入浅出C# async/await】理解 awaitable-awaiter 模式
part3 - 【深入浅出C# async/await】运行时上下文
part4 - 【深入浅出C$ async/await】自定义Task


读完前三章相信大家已经对async,await,task有一个大概的了解。那么接下来,我们尝试以下自己写一个Task。
首先引用Part3末尾的结论,一个对象必须具有以下条件才是可以等待的:

  1. 它有一个 GetAwaiter() 方法(实例方法或扩展方法)。
  2. 它的 GetAwaiter() 方法返回一个awaiter。如果满足以下条件,编译器会判定对象是awaiter:
    • 它实现了 INotifyCompletion 或 ICriticalNotifyCompletion 接口;
    • 它有一个 IsCompleted 属性,它有一个 getter 方法并返回一个Boolean;
    • 它有一个 GetResult() 方法,该方法返回 void(对应Task) 或 TResult(对应Task)。

自定义Task模板

public class MyTask : ICriticalNotifyCompletion
{
    #region MyTask必须包含以下方法,编译器才会认为MyTask是可等待的
    public bool IsCompleted
    {
        get { return true; } //默认MyTask是已经执行完的
    }

    public MyTask GetAwaiter()
    {
        return this; //相当于省略了awaitable,MyTask自己就是一个awaiter
    }

    public void GetResult()
    {
    }
    #endregion

    public void OnCompleted(Action continuation)
    {
    }

    public void UnsafeOnCompleted(Action continuation)
    {
    }
}

简单测试一下

public class TaskLikeTest
{
    public static void Main2_1()
    {
        TaskTest();
        Console.WriteLine("Main");
    }

    public static async void TaskTest()
    {
        await new MyTask();
        Thread.Sleep(4000);
        Console.WriteLine("Test");
    }
}
Test
Main

可以看到MyTask实例可以被await。
除此之外,我们可以注意到,TaskTest()实际上并不是一个异步的方法,TaskTest中Thread.Sleep(4000)会阻塞主线程,这是为什么呢?在Part1中我们曾经对异步方法源码进行过分析,现在反编译程序集看看是怎么回事:

public sealed class TaskTest_1 : IAsyncStateMachine
{
    public int state;

    public AsyncVoidMethodBuilder builder;

    public object u_1;

    private void MoveNext()
    {
        int num = state;
        try
        {
            MyTask awaiter;
            if (num != 0)
            {
                awaiter = new MyTask().GetAwaiter();
                if (!awaiter.IsCompleted) //关键
                {
                    num = (state = 0);
                    u_1 = awaiter;
                    TaskTest_1 stateMachine = this;
                    builder.AwaitUnsafeOnCompleted(ref awaiter,ref stateMachine);
                    return;
                }
            }
            else
            {
                awaiter = (MyTask)u_1;
                u_1 = null;
                num = state = -1;
            }
            awaiter.GetResult();
            Thread.Sleep(4000);
            Console.WriteLine("Test");
        }
        catch (Exception e)
        {
            state = -2;
            builder.SetException(e);
            return;
        }
        state = -2;
        builder.SetResult();
    }

    void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
    {
        this.builder.SetStateMachine(stateMachine);
    }
    
    void IAsyncStateMachine.MoveNext()
    {
        MoveNext();
    }
}

public static void taskTest_1()
{
    TaskTest_1 stateMachine = new TaskTest_1();
    stateMachine.builder = AsyncVoidMethodBuilder.Create();
    stateMachine.state = -1;
    stateMachine.builder.Start(ref stateMachine);
}

在反编译的方法中,stateMachine.builder.Start(ref stateMachine)启动了状态机,但是此时IsCompleted为true,也就是说MyTask已经完成,看代码,我们会同步执行await之后的代码。
总结: 状态机启动 —> 获得MyTask的awaiter —> 发现awaiter.IsCompleted = true —> 执行GetResult()之后的代码。

关于状态机流程的解释,我发现自己写的并不是很好,您可以看这篇文章对状态机流程和AwaitUnsafeOnCompleted进行深入了解:.NET Task 揭秘(2):Task 的回调执行与 await

在Part1中我们用Task.ContinueWith替换了整个状态机,我们把await后面的代码作为回调传入Task.ContinueWith()。AwaitOnCompleted与之同理,我们修改一下MyTask。

public enum AwaiterStatus : byte
{
    Pending = 0,
    Succeeded = 1,
    Faulted = 2
}

public class MyTask : ICriticalNotifyCompletion //和ICriticalNotifyCompletion的区别在于不会同步ExcutionContext
{
    public AwaiterStatus state;
    public object callback; 

    #region MyTask必须包含以下方法,编译器才会认为MyTask是可等待的
    public bool IsCompleted
    {
        get { return state!=AwaiterStatus.Pending; } //默认MyTask是已经执行完的
    }

    public MyTask GetAwaiter()
    {
        return this; //相当于省略了awaitable,MyTask自己就是一个awaiter
    }

    public void GetResult()
    {
        switch (this.state)
        {
            case AwaiterStatus.Succeeded:
                break;
            case AwaiterStatus.Faulted:
                ExceptionDispatchInfo c = this.callback as ExceptionDispatchInfo;
                this.callback = null;
                c?.Throw();
                break;
            //task执行完了状态还是Pending,显然是异常
            default:
                throw new NotSupportedException("ETTask does not allow call GetResult directly when task not completed.Please use await");
        }
    }
    #endregion

    //SetResult设置State,并执行回调
    public void SetResult()
    {
        if (state != AwaiterStatus.Pending)
        {
            throw new InvalidCastException("TaskT_TransitionToFinal_AlreadyCompleted");
        }

        state = AwaiterStatus.Succeeded;
        Action continuation = this.callback as Action;
        continuation?.Invoke();
    }
    
    public void OnCompleted(Action continuation)
    {
    	this.UnsafeOnCompleted(continuation);
    }

    //把await之后的代码作为回调传入
    public void UnsafeOnCompleted(Action continuation)
    {
        if (this.state != AwaiterStatus.Pending)
        {
            continuation?.Invoke();
            return;
        }
        this.callback = continuation;
    }

    //通知 C# 编译器将该方法内联处理.
    //内联的好处是避免方法调用的额外开销,但是会让编译后的代码体积变大
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    [DebuggerHidden]
    public void SetException(Exception e)
    {
        if (this.state == AwaiterStatus.Pending)
        {
            throw new InvalidCastException("当前状态为success/faulted,不能再转换状态了");
        }
    
        this.state = AwaiterStatus.Faulted;
    }
}

测试:

public static void Main2_1()
{
    TaskTest();
    Console.WriteLine("Main");
    while (true){}
}

public static async void TaskTest()
{
    MyTask tcs =  new MyTask();
    ThreadPool.QueueUserWorkItem(state =>
    {
        Thread.Sleep(2000);
        tcs.SetResult();
    });
    await tcs;
    Console.WriteLine("Test");
}

Main
Test

Task-Like Property

下面的方法,在编译器中会报错。

请添加图片描述

对异步方法进行反编译后,能看到对应生成的状态机类里有一个Builder成员。例如上面的TaskTest_1类中,可以看到Builder的类型为AsyncVoidBuilder。
对于我们自定义的Task,C#找不到对应的Builder,因此需要我们自己定义。
如果您想深入了解AsyncMethodBuilder,请看 .NET Task 揭秘(3)async 与 AsyncMethodBuilder
为MyTask添加标签。

[AsyncMethodBuilder(typeof(MyAsyncTaskMethodBuilder))] 
//没有builder,MyTask不能作为函数返回值,编译器会报错不是一个task-like类型

MyAsyncTaskMethodBuilder结构如下:

//请结合上面反编译代码中的TaskTest_1阅读
public struct MyAsyncTaskMethodBuilder
{
    private MyTask tcs;
    
    //Builder必须有以下方法/成员,不然编译器不能通过编译!!!
    //1. 创建方法
    [DebuggerHidden]
    public static MyAsyncTaskMethodBuilder Create()
    {
        MyAsyncTaskMethodBuilder builder = new MyAsyncTaskMethodBuilder() { tcs = new MyTask() };
        return builder;
    }
    
    //2. TaskLike Task property
    [DebuggerHidden] 
    public MyTask Task => this.tcs;
    
    //3. SetException
    [DebuggerHidden]
    public void SetException(Exception exception)
    {
        this.tcs.SetException(exception);
    }
    
    //4. SetResult
    [DebuggerHidden]
    public void SetResult()
    {
        this.tcs.SetResult();
    }
    
    //5. AwaitOnCompleted
    //不会用到这个方法,因为我们不需要同步executionContext
    [DebuggerHidden]
    public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine
    {
        awaiter.OnCompleted(stateMachine.MoveNext); 
    }
    
    //6.AwaitUnsafeOnCompleted --- 可以看Task的
    [DebuggerHidden]
    public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine
    {
        awaiter.OnCompleted(stateMachine.MoveNext);
    }
    
    //7. start启动状态机
    [DebuggerHidden]
    public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
    {
        stateMachine.MoveNext();
    }
    
    //8. SetStateMachine
    [DebuggerHidden]
    public void SetStateMachine(IAsyncStateMachine stateMachine)
    {
    }
}

测试

public static void Main2_1()
{
    TaskTest3();
    Console.WriteLine("Main");
    while (true)
    {
    }
}

static async void TaskTest3()
{
    await TaskTest2();
    Thread.Sleep(1000);
    Console.WriteLine("TaskTest3");
}

static async MyTask TaskTest2()
{
    await Task.Delay(3000);
    Console.WriteLine("TaskTest2");
}

Main
TaskTest2
TaskTest3
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
C#中,async/await是一种用于处理异步操作的关键字组合。它可以使代码更加简洁和易于理解,同时也提供了更好的性能和可维护性。 下面是一些C# async/await的复杂运用场景: 1. 并行执行多个异步任务:使用Task.WhenAll方法可以并行执行多个异步任务,并等待它们全部完成。例如: ```csharp public async Task DoMultipleTasksAsync() { Task task1 = Task.Run(() => DoTask1()); Task task2 = Task.Run(() => DoTask2()); Task task3 = Task.Run(() => DoTask3()); await Task.WhenAll(task1, task2, task3); } ``` 2. 异步任务的超时处理:使用Task.Delay和CancellationToken实现异步任务的超时处理。例如: ```csharp public async Task DoTaskWithTimeoutAsync() { var cts = new CancellationTokenSource(); cts.CancelAfter(TimeSpan.FromSeconds(5)); try { await Task.Delay(TimeSpan.FromSeconds(10), cts.Token); // 执行异步任务 } catch (OperationCanceledException) { // 超时处理逻辑 } } ``` 3. 异步任务的重试机制:使用循环和延迟重试策略来实现异步任务的重试。例如: ```csharp public async Task DoTaskWithRetryAsync() { int maxRetries = 3; int retryDelaySeconds = 5; for (int retryCount = 0; retryCount < maxRetries; retryCount++) { try { // 执行异步任务 await DoTaskAsync(); return; } catch (Exception) { // 异常处理逻辑 } await Task.Delay(TimeSpan.FromSeconds(retryDelaySeconds)); } // 重试次数超过限制的处理逻辑 } ``` 这些是C# async/await的一些复杂运用场景,希望对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值