当需要把一些异步功能扩展支持async/await时一般都需要使用到TaskCompletionSource。但由于是强泛型类型所以在使用时有时的确不太方便的,为了更好地适配不同情况因此TaskCompletionSource的基础上扩展出更灵活的对应功能类。
最近打算在MQTT协议的基础上扩展出远程接口调用功能,由于涉及到网络异操作,而DispatchProxy实现方法则是基本同步完成的,所以需在实现过程即用了TaskCompletionSource。
public class ClientDispatch : DispatchProxy
{
protected override object Invoke(MethodInfo targetMethod, object[] args)
{
if (!mHandlers.TryGetValue(targetMethod.Name, out ActionHandler handler))
{
var error = new Exception($"{targetMethod.Name} action not found!");
throw error;
}
else
{
if (!handler.IsTaskResult)
{
var error = new Exception("Definition is not supported, please define task with return value!");
throw error;
}
var result = await NetInvoke;//网络处理
return Convter(result)
}
}
}
以上伪代码看上去很正常,但这个虚方法是无法标记为async处理,所以以上await代码不能使用的;显然这里单个TaskCompletionSource是无法完成的,网络调用也有一个TaskCompletionSource过程,而这里则需要两个TaskCompletionSource串联起来;为了更好地处理这一过程派生出一个新的功能类。
interface IAnyCompletionSource
{
void Success(object data);
void Error(Exception error);
void WaitResponse(Task<object> task);
Task GetTask();
}
class AnyCompletionSource<T> : TaskCompletionSource<T>, IAnyCompletionSource
{
public void Success(object data)
{
TrySetResult((T)data);
}
public void Error(Exception error)
{
TrySetException(error);
}
public async void WaitResponse(Task<object> task)
{
try
{
var response = await task;
if (response != null)
{
Success(response);
}
else
{
Success(new object());
}
}
catch (Exception e_)
{
TrySetException(e_);
}
}
public async void SetTimeOut(int timeout, string message)
{
try
{
await System.Threading.Tasks.Task.Delay(timeout);
if (!this.Task.IsCompleted)
TrySetException(new TimeoutException(message));
}
catch { }
}
public Task GetTask()
{
return this.Task;
}
}
新的类提供了一个WaitResponse方法,可以让这个TaskCompletionSource的完成依赖于另一个Task的完成情况;有了这个扩展那对于接口远程调用的实现就会简单很多了。
protected override object Invoke(MethodInfo targetMethod, object[] args)
{
if (!mHandlers.TryGetValue(targetMethod.Name, out ActionHandler handler))
{
var error = new MQTTException($"{targetMethod.Name} action not found!");
throw error;
}
else
{
if (!handler.IsTaskResult)
{
var error = new MQTTException("Definition is not supported, please define task with return value!");
throw error;
}
var request = new Publish();
request.Topic = handler.Url;
request.Payload = Client.DataFormater.EncodeParameters(args);
var task = Client.SendWait(request);
IAnyCompletionSource source = handler.GetCompletionSource();
source.WaitResponse(task, Client, handler.ResponseType);
return source.GetTask();
}
}