嵌套异步任务

3

我想知道是否有可能改进这段代码以获得更好的性能。我对服务器端的异步操作还不熟悉,所以请您耐心等待:

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吗?另外,为什么你的一些方法使用continuations而另一些使用Task?我认为如果你保持一致会更好。 - svick
@svick 我正在使用VS2012,但我相信它目前是.NET 4.5。 - Johan
1
@Johan 这不是打字错误 - 它是 C#5 (语言) 和 .NET 4.5 (框架) ;) - Reed Copsey
3个回答

7
理想情况下,你应该将这些操作完全异步化:
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),您可以简化您的代码为:
```html

如果这不是一个选项(即:您没有使用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);
    });

});

1
@Johan 听起来你的 GetPlayer 方法不是线程安全的 ;) - Reed Copsey
1
@Johan 不知道具体情况很难说 - 顺便提一下,如果你能让第一个选项起作用的话,那将会是非常优秀的选择(完全没有阻塞)。 - Reed Copsey
@Johan 你需要将你的方法改为异步方法。 - Reed Copsey
@Johan 这是 con.GetGame(id, async (game) => { - 虽然不确定上下文,但可能不容易修改以正确处理 Task<T>。 - Reed Copsey
我会深入研究这个问题。Reed,非常感谢你的帮助,谢谢你抽出时间! - Johan
显示剩余3条评论

0

没有使用LINQ的替代方案(尽管我喜欢Reed Copsey的解决方案)。但是,请注意,正如评论中指出的那样,此解决方案通过将对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),我强烈建议阅读:基于任务的异步模式 - 由微软的Stephen Toub编写。

2
这个代码可以正常运行并且看起来很好,但是不必要地使用了线程池... Task.Run 会依赖于线程池中的线程来运行任务,而我的版本避免了这种情况。 - Reed Copsey
@ReedCopsey 我完全同意。我在写答案时应该指出这一点。不过,我现在已经更新了我的答案,所以清楚地表明这种替代实现会在多次调用 Task.Run() 时引入开销。 - Lasse Christiansen

0

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


你介意给我展示一下在我的例子中如何实现吗? - Johan
@Johan 请考虑其他答案,并尽量避免复制粘贴编程。 - Iłya Bursov
3
这不是关于复制粘贴编程的问题,而是理解您的意思以及如何实际使用“Parallel.ForEach()”。 - svick
Parallel.ForEach(game.Team1, p => { p.SomeExtraDetails = GetPlayerDetails(p.Id); }); - Iłya Bursov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接