async await 语法是一个非常好用的异步编程语法,它能够非常轻松的实现异步代码的编写,同时符合人类的思维习惯。如果我想让这种异步状态机,能工作在我自定义的线程里面要怎么办呢?
专注于.NET技术开发的博主,关注我个人微信公众号查看更多:承哥技术交流小作坊。
要让编译器识别async await 语法首先需要具备以下条件:
可等待的对象,需要实现`T GetAwaiter();`方法。
注意:`T`可以是任何类型,不一定是`object`类型。
可等待对象`无返回值`需要满足的条件:
-
继承接口`INotifyCompletion`或实现`void OnCompleted(Action continuation);`方法。
-
实现`bool IsCompleted { get; }`属性。
-
实现`void GetResult();`方法。
可等待对象`带返回值`需要满足的条件:
-
继承接口`INotifyCompletion`或实现`void OnCompleted(Action continuation);`方法。
-
实现`bool IsCompleted { get; }`属性。
-
实现`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技术开发的博主,关注我个人微信公众号查看更多:承哥技术交流小作坊。