async await 实现自定义的可等待任务

async await 语法是一个非常好用的异步编程语法,它能够非常轻松的实现异步代码的编写,同时符合人类的思维习惯。如果我想让这种异步状态机,能工作在我自定义的线程里面要怎么办呢?

专注于.NET技术开发的博主,关注我个人微信公众号查看更多:承哥技术交流小作坊。

要让编译器识别async await 语法首先需要具备以下条件:  

        可等待的对象,需要实现`T GetAwaiter();`方法。

        注意:`T`可以是任何类型,不一定是`object`类型。

可等待对象`无返回值`需要满足的条件:

  1. 继承接口`INotifyCompletion`或实现`void OnCompleted(Action continuation);`方法。 

  2. 实现`bool IsCompleted { get; }`属性。

  3. 实现`void GetResult();`方法。       

可等待对象`带返回值`需要满足的条件:

  1. 继承接口`INotifyCompletion`或实现`void OnCompleted(Action continuation);`方法。 

  2. 实现`bool IsCompleted { get; }`属性。

  3. 实现`T GetResult();`方法。`T`可以是任意类型。       

具备上述条件,你的代码就可以顺利编译通过了。但是此时的代码,还不能工作,因为你还没有给状态机增加状态!为了增加这个状态,我们增加一个方法`void ReportCompleted(T result);`,用来更改状态机的状态值,表示我们要执行的异步操作是否已完成。

具体代码,如下所示:

    /// <summary>
    /// 带返回值的可等待对象
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface IAwaitable<T> : INotifyCompletion
    {
        bool IsCompleted { get; }
        T Result { get; }
        T GetResult();
        void ReportCompleted(T result);
    }

    /// <summary>
    /// 不带返回值的可等待对象
    /// </summary>
    public interface IAwaitable : INotifyCompletion
    {
        bool IsCompleted { get; }
        void GetResult();
        void ReportCompleted();
    }

    /// <summary>
    /// 获取可等待者
    /// </summary>
    public interface IAwaiter
    {
        IAwaitable GetAwaiter();
    }

    /// <summary>
    /// 获取可等待者
    /// </summary>
    public interface IAwaiter<T>
    {
        IAwaitable<T> GetAwaiter();
    }
    
    /// <summary>
    /// 不带返回值的可等待的任务
    /// </summary>
    public class AwaitableTask : IAwaiter
    {
        public AwaitableTask()
        {
            AwaitableObject = new AwaitableObject();
        }

        /// <summary>
        /// 获取可等待的对象
        /// </summary>
        public IAwaitable GetAwaiter()
        {
            return AwaitableObject;
        }

        /// <summary>
        /// 实现相关接口的可等待的对象
        /// </summary>
        public AwaitableObject AwaitableObject { get; private set; }
    }

    /// <summary>
    /// 带返回值的可等待的任务
    /// </summary>
    public class AwaitableTask<T> : IAwaiter<T>
    {
        public AwaitableTask()
        {
            AwaitableObject = new AwaitableObject<T>();
        }

        public IAwaitable<T> GetAwaiter()
        {
            return AwaitableObject;
        }

        public AwaitableObject<T> AwaitableObject { get; private set; }
    }
    
    /// <summary>
    /// 不带返回值的可等待的状态对象
    /// </summary>
    public class AwaitableObject : Awaitable, IAwaitable
    {
        public void GetResult()
        {

        }
    }

    /// <summary>
    /// 带返回值的可等待的状态对象
    /// </summary>
    public class AwaitableObject<T> : Awaitable, IAwaitable<T>
    {
        public T Result { get; private set; }

        public T GetResult()
        {
            return Result;
        }

        public void ReportCompleted(T result)
        {
            Result = result;
            ReportCompleted();
        }
    }

    /// <summary>
    /// 可等待的抽象类,状态机的具体实现
    /// </summary>
    public abstract class Awaitable
    {
        private Action _continuation;

        private readonly object _locker = new object();

        public bool IsCompleted { get; private set; }

        public void OnCompleted(Action continuation)
        {
            //因为此处调用和ReportCompleted,可能不在一个线程上,会出现时机问题
            lock (_locker)
            {
                if (IsCompleted)
                {
                    continuation?.Invoke();
                }
                else
                {
                    _continuation += continuation;
                }
            }
        }

        //用于通知,当前任务已经完成。
        public void ReportCompleted()
        {
            lock (_locker)
            {
                IsCompleted = true;
                var continuation = _continuation;
                _continuation = null;
                continuation?.Invoke();
            }
        }

        //重置状态,让状态机恢复可等待
        public void Reset()
        {
            lock (_locker)
            {
                _continuation?.Invoke();
                IsCompleted = false;
                _continuation = null;
            }
        }
    }

上述代码就可以完整的实现自定义的可等待方法了。主要用途可以用在针对一些事件触发的代码块中,或者在依赖属性中。这样我们就可以以同步的方法来编写我们的代码了。

我们可以来看下简单的示例:


    public class TestElement : UIElement
    {
        //实例化一个可等待任务
        AwaitableTask _awaitableTask = new AwaitableTask();

        /// <summary>
        /// 指定的资源的路径
        /// </summary>
        public string SourceUri
        {
            get { return (string)GetValue(SourceUriProperty); }
            set { SetValue(SourceUriProperty, value); }
        }

        private ImageSource gifSource;

        public static readonly DependencyProperty SourceUriProperty = DependencyProperty.Register(nameof(SourceUri), typeof(string), typeof(TestElement), new PropertyMetadata(string.Empty, async (s, e) =>
        {
            var loc = s as TestElement;
            loc.gifSource = await LoadGifSourceAsync(e.NewValue as string);
            loc._awaitableTask.AwaitableObject?.ReportCompleted();
        }));

        public static async Task<ImageSource> LoadGifSourceAsync(string url)
        {
            await Task.Delay(500);
            return new BitmapImage(new Uri(url));
        }

        /// <summary>
        /// 等待源数据加载完成,对外开放的方法
        /// </summary>
        /// <returns></returns>
        public async Task WaitForLoaded()
        {
            await _awaitableTask;
        }

        public bool Do()
        {
            if (gifSource != null)
                return true;            
            return false;
        }
    }

    public class TestClass
    {
        public TestClass()
        {
            TestForNotWait();
            TestForWait();
        }


        public async void TestForWait()
        {
            TestElement testElement = new TestElement
            {
                SourceUri = @"D:\Users\huc\Pictures\Saved Pictures\1.jpeg"
            };
            await testElement.WaitForLoaded();
            Console.WriteLine("TestForWait Result:" + testElement.Do());
        }

        public void TestForNotWait()
        {
            TestElement testElement = new TestElement
            {
                SourceUri = @"D:\Users\huc\Pictures\Saved Pictures\1.jpeg"
            };

            Console.WriteLine("TestForNotWait Result:" + testElement.Do());            
        }
    }    

专注于.NET技术开发的博主,关注我个人微信公众号查看更多:承哥技术交流小作坊。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冷眼Σ(-᷅_-᷄๑)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值