正在结束具有部分类型信息的异步委托调用

本文关键字:信息 异步 调用 类型 结束 | 更新日期: 2023-09-27 17:48:52

使用BeginInvoke/EndInvoke模式编写异步方法实现时,代码可能如下所示(为了避免您猜测这是缓存周围的异步包装):

IAsyncResult BeginPut(string key, object value)
{
    Action<string, object> put = this.cache.Put;
    return put.BeginInvoke(key, value, null, null);
}
void EndPut(IAsyncResult asyncResult)
{
    var put = (Action<string, object>)((AsyncResult)asyncResult).AsyncDelegate;
    put.EndInvoke(asyncResult);
}

这非常有效,因为它知道委托的类型,所以可以强制转换。然而,当您有两个Put方法时,它开始变得混乱,因为尽管该方法返回void,但您似乎必须将其强制转换为强类型委托才能结束调用,例如

IAsyncResult BeginPut(string key, object value)
{
    Action<string, object> put = this.cache.Put;
    return put.BeginInvoke(key, value, null, null);
}
IAsyncResult BeginPut(string region, string key, object value)
{
    Action<string, string, object> put = this.cache.Put;
    return put.BeginInvoke(region, key, value, null, null);
}
void EndPut(IAsyncResult asyncResult)
{
    var put = ((AsyncResult)asyncResult).AsyncDelegate;
    var put1 = put as Action<string, object>;
    if (put1 != null) 
    {
        put1.EndInvoke(asyncResult);
        return;
    }
    var put2 = put as Action<string, string, object>;
    if (put2 != null) 
    {
        put2.EndInvoke(asyncResult);
        return;
    }
    throw new ArgumentException("Invalid async result", "asyncResult");
}

我希望有一种更干净的方法来做这件事,因为我唯一关心的是委托的返回类型(在这种情况下是无效的),而不是提供给它的论点。但我绞尽脑汁,问了办公室里的其他人,没有人能想到答案。

我知道一个解决方案是编写一个自定义的IAsyncResult,但这是一项非常困难的任务,因为围绕WaitHandle的懒惰实例化之类的事情存在潜在的线程问题,所以我宁愿有这个看起来有点粗糙的代码,也不愿走这条路。

关于如何在没有级联is检查的情况下结束调用,有什么想法吗?

正在结束具有部分类型信息的异步委托调用

我错了,有一种更干净的方法。

您可以在已经知道特定委托类型的同一上下文中为特定EndInvoke()方法创建Action( IAsyncResult )委托,并将其作为AsyncState传递。为了方便起见,我将传递EndPut()作为回调。

IAsyncResult BeginPut( string key, object value ) {
    Action<string, object> put = this.Put;
    return put.BeginInvoke( key, value, EndPut,
        new Action<IAsyncResult>( put.EndInvoke ) );
}
IAsyncResult BeginPut( string region, string key, object value ) {
    Action<string, string, object> put = this.Put;
    return put.BeginInvoke( region, key, value, EndPut,
        new Action<IAsyncResult>( put.EndInvoke ) );
}

然后你完成它。

void EndPut( IAsyncResult asyncResult ) {
    var del = asyncResult.AsyncState as Action<IAsyncResult>;
    del( asyncResult );
}

为什么不回到更通用的过载:来避免这个问题

IAsyncResult BeginPut(string key, object value) {
   return this.BeginPut(null, key, value);
}
IAsyncResult BeginPut(string region, string key, object value) {
   Action<string, string, object> put = this.Put;
   return put.BeginInvoke(region, key, value, null, null);
}
void EndPut(IAsyncResult asyncResult) {
   var put = (Action<string, string, object>)((AsyncResult)asyncResult).AsyncDelegate;
   put.EndInvoke(asyncResult);
}

编辑:请查看我的其他答案。它可以更清洁。

我不认为有什么办法可以让这里更干净。通过使用相同的EndPut方法结束多种类型的异步调用,您基本上使这一点不可避免。您也可以遵循正常模式,将EndPutput委托作为最后两个参数传递给BeginInvoke()(回调和异步状态),并且您仍然需要测试委托的类型才能调用EndInvoke()

将它们选为Delegate根本没有帮助。

我喜欢Mark Brackett的想法。我认为这可以归结为以下几种选择:

  • 通过让一个重载调用另一个来完全加入它们。一个委托,一个回调
  • 通过具有两个用于调用EndInvoke()的回调来完全分离它们

除此之外,唯一需要做的就是让代码稍微干净一点,并使用switch或使用asyncResult.AsyncState.GetType()的查找字典,将put委托作为状态对象传递。