如何并行执行多个异步调用?

时间:2011-10-26 10:27:40

标签: c# asynchronous

我有许多命令可以调用soap Web服务(Betfair API)。所有都是经典的异步编程模型类型......

public void DoXXX( <input parameters ...> )
{
    XXXRequest Request = new XXXRequest();
    // populate Request from input parameters ...
    BetfairService.BeginXXX( Request, XXXCallback, State );
}

private void XXXCallback(IAsyncResult Result)
{
    XXXResponse Response = BetfairService.EndXXX(Result);
    if (Response.ErrorCode == XXXErrorCode.OK)
        // store data from Response
    else
        // deal with error
}

我想执行一组指定的命令,然后在完成所有命令后使用组合的返回数据值进行一些计算。

我可以通过创建一个命令队列来执行此操作,并使每个回调方法在完成后触发队列中的下一个命令,并将计算作为队列中的最后一项。然而,这相对较慢。

我理想的解决方案是让所有这些命令并行运行,然后在完成所有命令后触发计算。我已经尝试过查看Task.Factory.FromAsync(),但我找到的所有示例都只包括对BeginXXX / EndXXX的直接调用,而没有对响应做任何事情。

有没有人有任何针对此问题的合适解决方案?

3 个答案:

答案 0 :(得分:2)

要使用FromAsync,您需要指定返回类型:

var task = Task<XXXResponse>.Factory.FromAsync( ...

然后,您的任务的Result属性为XXXResponse

然后,您可以使用Parallel.Invoke并行运行初始命令。这将阻止所有这些任务完成。然后你可以进行“额外处理”。

或者您可以将初始任务存储在数组中,并使用Task.Factory.ContinueWhenAll创建延续。

尼克

答案 1 :(得分:1)

我建议您查看Microsoft的Reactive Extensions(Rx)来执行您想要的操作。它允许您将异步操作(以及其他内容)转换为可观察的LINQ查询。

假设我有这三个函数,每个函数都需要花费大量时间来计算:

Func<int> fa = () =>
{
    Thread.Sleep(2000);
    return 42;
};

Func<int, string, string> fb = (n, t) =>
{
    Thread.Sleep(n * 1000);
    return t + n.ToString();
};

Func<DateTimeOffset> fc = () =>
{
    Thread.Sleep(1000);
    return DateTimeOffset.UtcNow;
};

然后我可以使用FromAsyncPattern方法将这些lambda函数转换为可观察的函数:

Func<IObservable<int>> ofa =
    Observable
        .FromAsyncPattern<int>(
            fa.BeginInvoke,
            fa.EndInvoke);

Func<int, string, IObservable<string>> ofb =
    Observable
        .FromAsyncPattern<int, string, string>(
            fb.BeginInvoke,
            fb.EndInvoke);

Func<IObservable<DateTimeOffset>> ofc =
    Observable
        .FromAsyncPattern<DateTimeOffset>(
            fc.BeginInvoke,
            fc.EndInvoke);

现在我可以通过这样做来启动所有调用:

IObservable<int> oa = ofa();
IObservable<string> ob = ofb(1, "foo");
IObservable<DateTimeOffset> oc = ofc();

有效地并行开始三次计算。现在我们只需将结果汇总在一起。

这就是LINQ的用武之地:

var query =
    from a in oa
    from b in ob
    from c in oc
    select new { a, b, c };

然后我订阅此查询以获得结果:

query.Subscribe(p =>
{
    Console.WriteLine(p.a);
    Console.WriteLine(p.b);
    Console.WriteLine(p.c);
});

在我的测试中,我将计时器放在这个代码周围来计算实际执行时间。即使串联运行总时间应为4秒,此代码也会以2结束 - 三者中的任何一个的最长时间。

现在这个例子只是Rx可以做的一点方面,但它是一个很好的起点。

如果我能进一步解释,请大声说出来。

以下是Rx的链接:

答案 2 :(得分:0)

您应该拥有已执行服务调用的计数器。在每个回调方法中,您应该检查此计数器 - 如果它等于服务调用的最大数量,您应该进行额外的处理,否则 - 您只需增加计数器。