65.9K
CodeProject 正在变化。 阅读更多。
Home

如何使用委托管理多个异步调用

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.67/5 (8投票s)

2010年8月9日

CPOL

2分钟阅读

viewsIcon

39198

downloadIcon

376

如何使用委托管理多个异步调用

引言

在 .NET 中进行异步调用有很多种方法,你应该根据不同的场景选择使用哪一种。我在这里要讨论的场景是,当你需要进行多个异步调用,并且需要等待所有调用完成后才能进行下一步操作时该如何处理。

这是一种典型的情况,你需要进行多个数据库调用,并且在继续你的流程之前等待所有结果集返回。

问题

在这个例子中,你有一个方法 SomeRandomNumber,它需要一些时间来执行,并且会返回一个整数值。你需要对该方法进行 3 次异步调用,并等待所有调用完成。

private static int SomeRandomNumber(int waitTime)
{
    Thread.Sleep(waitTime);
    return rnd.Next();
}

最常见的解决方案之一是将所有异步调用放入 ThreadPool,并且你需要一个循环来检查它们是否都已完成,基于你实例中的某个全局布尔标志。首先,代码不够清晰,并且这种命令式风格在多线程情况下难以控制。你无法确定其他人不会更改你的标志!

private int? threadOneResult;
private int? threadTwoResult;
private int? threadThreeResult;

public void AsyncCallsWithThreadPool()
{
    ThreadPool.QueueUserWorkItem(ThreadOne, 1000);
    ThreadPool.QueueUserWorkItem(ThreadTwo, 5000);
    ThreadPool.QueueUserWorkItem(ThreadThree, 10000);

    while (threadOneResult == null ||
        threadTwoResult == null ||
        threadThreeResult == null)
    {
        Thread.Sleep(100);
    }

    // continue
    Console.WriteLine(
        String.Format("Result: {0}\t{1}\t{2}",
        threadOneResult,
        threadTwoResult,
        threadThreeResult));
}

ThreadOneThreadTwoThreadThree 将结果放回 global int? 变量中。当所有三个线程完成后,上述方法将退出 while 循环。

private void ThreadOne(object state)
{
    threadOneResult = SomeRandomNumber((int)state);
}

private void ThreadTwo(object state)
{
    threadTwoResult = SomeRandomNumber((int)state);
}

private void ThreadThree(object state)
{
    threadThreeResult = SomeRandomNumber((int)state);
}

解决方案

BeginInvokeEndInvoke 方法将为你完成所有工作。BeginInvoke 将在新的线程中处理你的异步调用。EndInvoke 将从该线程返回结果。如果线程在调用 EndInvoke 时尚未完成,它将等待。你无需在主线程中实现你的 wait 循环。每个异步操作的状态都保存在 IAsyncResult 对象中,因此一个委托可以处理多个调用。

这种方法可以消除所有不必要的全局变量,因此你的异步逻辑不需要依赖于此实例中的状态。这看起来很清晰。最重要的是,这保持了你的线程结构简单。

public void AsyncCallsInDelegate()
{
    GetNumber deleg = new GetNumber(SomeRandomNumber);

    IAsyncResult rst1 = deleg.BeginInvoke(1000, null, null);
    IAsyncResult rst2 = deleg.BeginInvoke(5000, null, null);
    IAsyncResult rst3 = deleg.BeginInvoke(10000, null, null);

    Console.WriteLine(
        String.Format("Result: {0}\t{1}\t{2}",
        deleg.EndInvoke(rst1),
        deleg.EndInvoke(rst2),
        deleg.EndInvoke(rst3)));
}

在 .NET 3.5 中,生活变得更好了。你可以使用泛型委托 Func<int> 来节省声明新委托的工作。

public void AsyncCallsInGenericDelege()
{
    Func<int> deleg = new Func<int>(SomeRandomNumber);

    IAsyncResult rst1 = deleg.BeginInvoke(1000, null, null);
    IAsyncResult rst2 = deleg.BeginInvoke(5000, null, null);
    IAsyncResult rst3 = deleg.BeginInvoke(10000, null, null);

    Console.WriteLine(
        String.Format("Result: {0}\t{1}\t{2}",
        deleg.EndInvoke(rst1),
        deleg.EndInvoke(rst2),
        deleg.EndInvoke(rst3)));
}

历史

  • 2010 年 8 月 8 日 - CodeProject 的首次发布
© . All rights reserved.