如何正确连接多个异步方法的结果?

时间:2012-07-04 00:37:10

标签: c# .net silverlight asynchronous parallel-processing

在Silverlight应用程序中,我需要并行运行多个异步调用 - 它们将调用服务器来检查数据等。我的伪代码设置看起来像这样

public interface IAsyncVerifier
{
    event EventHandler OnCompleted;

    void Verify();
}

// Subscribe to verifier completion events
foreach (var verifier in this.VerifierEngine.GetVerifiers())
{
    var asyncVerifier = verifier as IAsyncVerifier;
    if (asyncVerifier == null || !asyncVerifier.IsVerifierRunning()) continue;

    asyncVerifier.OnCompleted += (s, a) => DoWhat(); // ????????
    asyncVerifier.Verify();
}

所以,在我看来,我有所有这些任务共同的界面。然后在执行之前我订阅了他们所有的活动并且......?

我需要以某种方式等待他们完成并继续我正在做的事情。如何正确编写剩余代码?我看到某种counter我每次调用Verify()时都会递增,并在每次调用OnCompleted时递减。但我没有全面了解如何将它包装在一起,所以它没有味道。此外,如果我理解正确(调用WCF服务)

,Silverlight会在后台线程上运行

编辑:

我正在使用第三方验证引擎。 this.Verifier.VerifierEngine不是我写的。我只能添加验证器到集合。通常它们在主线程上执行以验证属性等。我需要创建验证程序,使得回调服务器,因此在Silverlight中async。我不是手动处理线程,当我调用服务它返回主线程所以它很好,这部分隐藏我。我想要的就是能够“等待”直到所有验证者完成。我现在唯一能检查的方法就是我正在做的事情。

因此我的用户界面很古怪。完成修改后,用户点击“保存”并开始验证。我检查是否所有验证者IsVerifierRunning并向用户提供他需要重试的消息。我真的需要等待所有人完成然后继续。

  1. 我不需要担心线程,除非正确的解决方案涉及使用线程
  2. 我已经可以访问结果

4 个答案:

答案 0 :(得分:3)

我使用Linq来练习Rx 使用System.Reactive.Linq; Observable.FromEvent   压缩方法

  var InitNetCellCache = InitNetCell();
            InitNetCellCache.Subscribe((s) =>
            {
                Debug.WriteLine("init NetCell Cache OK.");

                //ScheduleCrrentThread(innerAction);
            });
            var InitKPIMapCache = InitKPIMapCell();
            var zipped = InitKPIMapCache.Zip(InitNetCellCache, (left, right) => left + " - " + right);

            zipped.Subscribe((s) =>
                {
                    //When all async methods are completed
                    runtime.Show();
                }
            );

  IObservable<object> InitNetCell()
    {
        IUnityContainer unityContainer = ServiceLocator.Current.GetInstance<IUnityContainer>();
        INetCellManager inc = unityContainer.Resolve<INetCellManager>();
        var NetCell = Observable.FromEvent<InitCacheCompletedDelegate, object>(
         h => ((object sendera) => h.Invoke(sendera)),
         h => inc.InitCacheCompleted += h,
         h => inc.InitCacheCompleted -= h);
         //Run you async method
        inc.InitCache();
        return from e in NetCell select e;
    }

答案 1 :(得分:2)

我也建议使用Reactive Framework(Rx)来做你想做的事。

首先,你需要一个能够将IAsyncVerifier变成一个可以启动验证过程并订阅OnCompleted的observable的函数。这是:

Func<IAsyncVerifier, IObservable<Unit>> startVerifier = av =>
    Observable.Create<Unit>(o =>
    {
        var subject = new AsyncSubject<Unit>();
        var s1 =
            Observable
                .FromEventPattern(
                    h => av.OnCompleted += h,
                    h => av.OnCompleted -= h)
                .Select(_ => Unit.Default)
                .Subscribe(subject);
        var s2 = subject.Subscribe(o);
        av.Verify();
        return new CompositeDisposable(s1, s2);
    });

此函数使用AsyncSubject来确保它捕获事件触发。

现在查询变得非常简单。

var query = (
    from av in VerifierEngine
        .GetVerifiers()
        .OfType<IAsyncVerifier>()
        .ToObservable()
    where !av.IsVerifierRunning()
    from result in startVerifier(av)
    select av)
        .ToArray();

在Rx世界中,因为这是一个SelectMany查询,所以默认是在线程池上运行它们 - 所以它是并行运行的。

最终.ToArray()调用与函数的可枚举版本略有不同。可观察版本将IObservable<T>(一个返回零或更多值的可观察对象)更改为IObservable<T[]>(一个可返回零个或多个值的单个数组的observable)。换句话说,这将等到所有验证器完成后再返回所有验证器。

现在您只需订阅查询并运行您想知道所有验证者已完成的任何代码。

query.Subscribe(avs => DoWhat());

如果你想在每次验证器完成时运行一些代码,那么你可以取消.ToArray()调用并订阅查询,如下所示:

query.Subscribe(av => { /* Each */ }, () => { /* All */ });

我希望这一切都很清楚。

答案 2 :(得分:1)

从我所听说过的你的要求来看,我认为一个简单的计数器就足够了。现在,我将使用与您发布的内容最接近的解决方案,并列出需要有效的假设才能使其正常工作。如果任何假设无效,请告诉我,我会适当更新代码。

如果您的循环在UI线程上运行,则可以如下所示。这里有几个假设。首先是我们在UI线程和验证ui线程上的执行的调用。这会使DispatcherSynchronizationContext生效。来自wcf的完成事件将在它们开始的同一个SynchronizationContext中执行。这将有效地序列化它们的执行,这将为您的代码提供非常单线程的行为,因此在计数器上不需要锁定/ etc。还有更多内容http://msdn.microsoft.com/en-us/library/z8chs7ft(v=vs.95).aspx。第二个假设是验证调用将始终完成,即使它们发生错误。

编辑以匹配取消订阅要求。承担任何拼写错误,因为我刚刚使用记事本。 EventHandler可能必须是特定类型的事件处理程序,具体取决于您的事件参数。

int activeVerifiers = 0;
List<VerificationCleanup> cleanups = new List<VerificationCleanup>();

EventHandler completionHandler = (s, a) => 
{
     activeVerifiers--;
     if(activeVerifiers == 0)
     {
          //execute some sort of message/notification/all complete event

          foreach(VerificationCleanup cleanup in cleanups)
          {
            cleanup.Cleanup();
          }
     }
}; 

foreach (var verifier in this.VerifierEngine.GetVerifiers())
{
    var asyncVerifier = verifier as IAsyncVerifier;
    if (asyncVerifier == null || !asyncVerifier.IsVerifierRunning()) continue;

    cleanups.Add(new VerificationCleanup(completionHandler, asyncVerifier));

    activeVerifiers++;
    asyncVerifier.OnCompleted += completionHandler
    asyncVerifier.Verify();
}

private class VerificationCleanup
{
    private EventHandler eventHandler = null;
    private IAsyncVerifier verifier = null;

    public VerificationCleanup(EventHandler eventHandler, IAsyncVerifier verifier)
    {
        this.eventHandler = eventHandler;
        this.verifier = verifier;
    }

    public void Cleanup()
    {
        if(this.verifier != null)
        {
            this.verifier.OnCompleted -= this.eventHandler;
        }
    }
}

答案 3 :(得分:0)

Silverlight确实拥有ManualResetEvent,可以满足您的需求。但是,代码需要与您拥有的代码不同。

您是否有特殊原因想要使用计数器和旋转循环?它不是那么漂亮,但它也不需要像其他方法那样多的代码更改。

埃里克