【问题标题】:Nested asynchronous tasks嵌套异步任务
【发布时间】:2013-10-21 16:08:28
【问题描述】:

我想知道是否可以改进此代码以获得更好的性能。我是服务器端整个异步的新手,所以请耐心等待:

con.GetGame(id, game => {

    foreach(Player p in game.Team1)
    {
        p.SomeExtraDetails = GetPlayerDetails(p.Id);
    }

    // I would like the player data to be set on all players
    // before ending up here
});

private PlayerDetails GetPlayerDetails(double playerId)
{
    var task = con.GetPlayer(playerId);

    PlayerDetails ret = null;

    Task continuation = task.ContinueWith(t =>
    {
        ret = t.Result;
    });

    continuation.Wait();

    return ret;
}

如果我做对了,continuation.Wait(); 会阻塞主线程。

有没有办法让任务同时运行?

【问题讨论】:

  • 你能用 C# 5.0 吗?另外,为什么你的一些方法使用延续和一些Tasks?我认为如果你保持一致会更好。
  • @svick 我使用的是 VS2012,但我相信它是 .NET 4.5 atm。
  • @Johan 这不是拼写错误 - 它是 C# 5(语言)和 .NET 4.5(框架);)

标签: c# asp.net task-parallel-library


【解决方案1】:

理想情况下,您应该让这些操作一直异步:

private Task<PlayerDetails> GetPlayerDetailsAsync(double playerId)
{
    return con.GetPlayer(playerId);
}

con.GetGame(id, game => {
    var tasks = game.Team1
                    .Select(p => new { Player=p, Details=GetPlayerDetailsAsync(p.Id)})
                    .ToList(); // Force all tasks to start...

    foreach(var t in tasks)
    {
        t.Player.SomeExtraDetails = await t.Details;
    }

    // all player data is now set on all players
});

如果这不是一个选项(即:您没有使用 VS 2012),您可以将代码简化为:

// This is a more efficient version of your existing code
private PlayerDetails GetPlayerDetails(double playerId)
{
    var task = con.GetPlayer(playerId);
    return task.Result;
}

con.GetGame(id, game => {
    // This will run all at once, but block until they're done
    Parallel.ForEach(game.Team1, p =>
    {
        p.SomeExtraDetails = GetPlayerDetails(p.Id);
    });

});

【讨论】:

  • 非常感谢您的详细回答。我试过你的最后一个例子,但我在return task.Result 遇到了一个异常:The Write method cannot be called when another write operation is pending."
  • @Johan 听起来您的 GetPlayer 方法不是线程安全的;)
  • @Johan 很难在不知道发生了什么的情况下判断 - 顺便说一句,如果你能让它工作(完全没有阻塞),上面的第一个选项会更好(完全没有阻塞)
  • 好的,我已经尝试了第一个。 await 关键字用The 'await' operator can only be used in a method or lambda marked with the 'async' modifier 突出显示
  • @Johan 你需要让你的方法成为异步方法。
【解决方案2】:

考虑在您的 GetGame 页面中使用 Parallel.ForEach 而不是 Task.ContinueWith

【讨论】:

  • 您介意在我的示例中告诉我它是如何实现的吗?
  • @Johan 考虑其他答案并尽量避免复制粘贴编程
  • @IlyaBursov 这与复制粘贴编程无关。这是关于了解您的确切含义以及您将如何实际使用Parallel.ForEach()
  • 从上面的答案复制粘贴`Parallel.ForEach(game.Team1, p => { p.SomeExtraDetails = GetPlayerDetails(p.Id); });`
【解决方案3】:

没有 LINQ 的替代解决方案(尽管我喜欢 Reed Copsey 的解决方案)。但是,请注意,正如 cmets 中所指出的,此解决方案通过将对 GetPlayerDetailsAsync() 的调用封装在由 Task.Run() 创建的任务中来引入开销。

需要 .NET 4.5 和 C# 5。

con.GetGame(id, game => {

    var tasks = new List<Task>();

    foreach(Player p in game.Team1)
    {
        tasks.Add(Task.Run(async () => p.SomeExtraDetails = await GetPlayerDetailsAsync(p.Id)));
    }

    Task.WaitAll(tasks.ToArray());
});

private Task<PlayerDetails> GetPlayerDetailsAsync(double playerId)
{
    return con.GetPlayerAsync(playerId);
});

此外,为了赶上 .NET 4.5 的基于任务的异步模式 (TAP),我强烈推荐阅读:Task-based Asynchronous Pattern - 微软的 Stephen Toub。

【讨论】:

  • 这工作并且看起来不错,但是不必要地使用了 ThreadPool...Task.Run 将依赖 TP 线程来运行任务,而我的版本避免了这种情况
  • @ReedCopsey 我完全同意这一点。我应该在写答案时指出这一点。但是,我现在已经更新了我的答案,因此很明显,这种替代实现会在多次调用 Task.Run() 时引入开销。
猜你喜欢
  • 2015-09-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-18
  • 1970-01-01
  • 2018-07-01
  • 1970-01-01
  • 2012-10-12
相关资源
最近更新 更多