使用部分类型信息结束异步委托调用

Ending asynchronous delegate invocation with partial type information

提问人:Greg Beech 提问时间:10/10/2008 最后编辑:Kevin FairchildGreg Beech 更新时间:11/15/2008 访问量:1703

问:

使用 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);
}

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

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");
}

我希望有一种更干净的方法来做到这一点,因为我唯一关心委托的是返回类型(在本例中为 void),而不是提供给它的参数。但是我绞尽脑汁,问过办公室里的其他人,没有人能想出答案。

我知道一种解决方案是编写一个自定义,但这是一项艰巨的任务,因为存在潜在的线程问题,例如延迟实例化,我宁愿拥有这个看起来有点笨拙的代码,也不愿走这条路。IAsyncResultWaitHandle

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

C# 异步 委托

评论


答:

0赞 Mark Brackett 10/10/2008 #1

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

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);
}

评论

0赞 Greg Beech 10/10/2008
不幸的是,Put(string,object) 方法不会在内部传递给 Put(string,string,object) 方法,并且将 null 作为第一个参数传递给后者是非法的 [这似乎很合理,因为它们的语义有些不同,尽管名称相似]。
0赞 Joel B Fant 10/10/2008 #2

编辑:请看我的其他答案。它可以更干净。

我认为没有办法让它更干净。实质上,通过使用相同的方法结束多种类型的异步调用,使这种情况不可避免。您也可以遵循正常模式,将 和 委托作为最后两个参数传递给 (callback 和 asyncstate),并且您仍然必须测试委托在 order call 中的类型。EndPutEndPutputBeginInvoke()EndInvoke()

把它们投射到根本无济于事。Delegate

我喜欢马克·布兰克特(Mark Brackett)的想法。我认为这归结为以下选项:

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

除此之外,唯一要做的就是让你的代码更干净一点,并使用 or 查找字典 ,将委托作为该状态对象传递。switchasyncResult.AsyncState.GetType()put

评论

0赞 Greg Beech 10/10/2008
不幸的是,正如 Mark 的回答所评论的那样,null 对于 Put(string,string,object) 的第一个参数是非法的,因此不可能重载相互调用。分离回调也是不可能的,因为 EndPut 需要可由客户端调用,并且必须按照约定调用它。
0赞 Greg Beech 10/10/2008
所以听起来好像没有什么可以做的:(我真正想要的是能够将其转换为 Action,因为我不关心参数类型,但似乎在 C# 中不可能有这种类型的事情,因为 Action<T> 不是 Action。
4赞 Joel B Fant 10/10/2008 #3

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

在已知委托的特定类型的同一上下文中为特定方法创建委托,并将其作为 AsyncState 传递。为了方便起见,我将其作为回调传递。Action( IAsyncResult )EndInvoke()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 );
}

评论

0赞 Greg Beech 10/10/2008
不错的:)我知道一定有办法做到这一点!谢谢!
0赞 Joel B Fant 10/10/2008
它也可以用于具有 out 参数的 EndInvoke() 调用,但需要以闭包的形式再进行一级抽象,将 EndInvoke(out myObj, asyncResult) 隐藏在 EndInvoke(asyncResult) 后面。
0赞 Greg Beech 10/10/2008
我只有像 Put 这样的方法返回 void,或者 Get 返回单个对象,所以看起来我可以用“delegate void EndInvokeAction(IAsyncResult asyncResult)”和“delegate T EndInvokeFunc<T>(IAsyncResult asyncResult)”来覆盖它们。这些实际上在缓存之外很有用!
0赞 Greg Beech 10/10/2008
或者多想一想,此解决方案可以只使用 Action<IAsyncResult> 和 Func<IAsyncResult, T>而无需定义任何新的委托。现在我真的很开心!