【问题标题】:Unit test for asynchronous delegate call in c#c#中异步委托调用的单元测试
【发布时间】:2011-09-01 00:25:14
【问题描述】:

我有一个函数创建了一个委托并在该对象上启动 BeginInvoke,另一个函数被传入以等待 EndInvoke:

    private static void DeploymentComponentThreadedCallBack(IAsyncResult ar)
    {
        var result = (AsyncResult)ar;
        var pluginExecuteAction = (Action<int, Guid, int, EnvironmentServerComponentSet, string>)result.AsyncDelegate;
        pluginExecuteAction.EndInvoke(ar);
        //report back to WCF service that thread is finished
    }

public void DeployComponent(byte[] resource, Guid componentGuid, string deploymentType, Dictionary<string, object> args)
{
  var asyncCallback = new AsyncCallback(DeploymentComponentThreadedCallBack);
  IDeployComponent plugin = GetPluginDelegate();
  Action<byte[], Guid, string, Dictionary<string, object>> pluginExecuteAction = plugin.DeployComponent;
  IAsyncResult ar = pluginExecuteAction.BeginInvoke(resource, componentGuid, deploymentType, args, asyncCallback, null);
}

我想对此进行单元测试,但是当我这样做时,DeploymentComponentThreadedCallBack 永远不会受到打击,显然 EndInvoke 调用也不会。我认为发生这种情况是因为测试在异步线程结束之前通过,因此线程在 EndInvoke 之前停止执行,但是有没有办法可以阻止这种情况发生,以便看到 EndInvoke 被命中?

干杯, 马特

【问题讨论】:

    标签: c# multithreading delegates


    【解决方案1】:

    我认为您的基本问题是您没有在 DeployComponent 方法上公开任何可以让您跟踪从那里开始的异步操作的内容。如果您从那里返回了IAsyncResult,您可以调用ar.AsyncWaitHandle.WaitOne() 等待它完成。

    【讨论】:

    • 遗憾的是,这是示例代码——实际上 DeployComponent 做了更多的事情,而且它已经有一个 bool 返回类型。不过感谢您的建议。
    • 嗯,你必须以某种方式解决这个问题。如果你想等待一个操作完成,你必须公开一些东西让调用者等待。在正常情况下,您继续而不等待完成,但在您的测试中,您等待它完成。实际上,您可以更改 DeployComponent 以遵循 APM 并在它启动的所有内容完成时报告完成。它可能需要一些重构。
    【解决方案2】:

    据我所知,AsyncResult 有一个标志 (IsCompleted),它告诉您操作是否正在进行。等待它(例如,原始地使用 while 循环),然后执行您的断言

    【讨论】:

    • 有这样一个标志,但我如何在调用对象中得到它?即如果 DeployComponent 方法在 Foo 对象上,并且在我的测试中我调用 Foo.DeployComponent(byteStream, new Guid(), "cmd", null);我从哪里获取 AsyncResult 以查看它是否已完成?
    • 我看不到你从哪里来。您将 Asyncresult 传递给第一个方法,因此您可以在传递给它之前等待它。
    • 啊,我明白你的意思了。那是行不通的,因为在正常情况下我不想等待它 - 只是为了这个测试。否则,异步执行它没有多大意义。
    • 马特,缺少的是你的测试——看不到你来自哪里。如果您不给自己访问 AsyncHandle 的权限,那么您可能不走运
    【解决方案3】:

    您只需创建一个注入点即可将异步调用转换为阻塞调用。例如:

     public class MethodInvoker
     {
         public virtual void Invoke(Action begin, Action<IAsyncResult> end)
         {
              begin.BeginInvoke(end, null);
         }
     }
    

    使用这样的单元测试版本:

     public class SynchronousInvoker : MethodInvoker
     {
         public override void Invoke(Action begin, Action<IAsyncResult> end)
         {
             begin();
             end();
         }
     }
    

    然后你会写这样的代码:

     _myMethodInvoker.Invoke(pluginExecuteAction, asyncCallback);
    

    在您的正常功能的上下文中,它是异步的。在您的单元测试中,您只需将 SynchronousInvoker 注入它的位置,它就会变成一个阻塞调用。

    【讨论】:

    • 这看起来是个不错的方法,但我无法对其进行调整以使其发挥作用。 BeginInvoke 需要 AsyncResult 类型的参数,而不是 Action,如果没有 Action,您就无法在其上调用 end()。
    猜你喜欢
    • 2017-06-23
    • 1970-01-01
    • 2017-09-24
    • 1970-01-01
    • 2013-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多