【问题标题】:return in "wait Task<string>.Run (..)" sometimes hangsreturn in "wait Task<string>.Run (..)" 有时会挂起
【发布时间】:2020-05-31 12:05:54
【问题描述】:

这是我的代码的复制品,wait Task&lt;string&gt;.Run (..) sometimes 中的返回挂起。如果失败,则主要是第一次通话。

我该如何改进它?

using System.Threading.Tasks;
using System.Diagnostics;

private void Button_Click(object sender, RoutedEventArgs e)
{
    // This can be a very huge list
    string[] servers = new string[] { "10.17.100.1", "10.17.100.10", "10.17.100.20" };

    // the max parallel tasks must be limited
    Parallel.ForEach(servers,
        new ParallelOptions { MaxDegreeOfParallelism = 10 },  
        (forServer) =>
    {
        this.Method1Async(forServer).Wait();
    });

    Debug.WriteLine("Finished");
}

private async Task Method1Async(string server)
{
    await this.Method2Async(server);
}

private async Task Method2Async(string server)
{
    Debug.WriteLine("> Method2Async");

    string result = await Task<string>.Run(() =>
    {

        Debug.WriteLine("  Method2Async before return");

        return GetDataFromServer(server);
    });

    Debug.WriteLine("< Method2Async");
}

private string GetDataFromServer(string server)
{
    // any long time running stuff

    Thread.Sleep(10000);

    return "the server data";
}

想要的输出:

> Method2Async
  Method2Async before return
< Method2Async
Finished

返回挂起时的输出:

> Method2Async
  Method2Async before return

【问题讨论】:

  • 不要在任务中使用.Wait()
  • “等待 Parallel.ForEach 完成”是什么意思?这是一个同步调用,直到所有并行任务完成后才返回。
  • IIRC,await 告诉编译器和运行时“重新洗牌,使函数的其余部分成为回调”。 Wait() 只是一个函数调用。 Await 与括号相当,甚至略高于括号。很容易让他们感到困惑。
  • 请更新您的问题,没有anydata
  • 您可能会觉得这很有趣:How to limit the amount of concurrent async I/O operations?。问题中还有一个嵌套的Parallel.ForEach,看起来像是成绩单错误。

标签: c# async-await parallel.foreach


【解决方案1】:

另一种选择是使用显然非常流行的AsyncEnumerator NuGet Package 中的ParallelForEachAsync

Awaitable foreach 构造可以通过任何IEnumerableIAsyncEnumerable. 的迭代来实现

using Dasync.Collections;

string[] servers = new string[] { "10.17.100.1", "10.17.100.10", "10.17.100.20" };

await servers.ParallelForEachAsync<string>(async forServer =>
{
    await this.Method1Async(forServer);

}, maxDegreeOfParallelism: 10);

为了收集返回值,可以使用thread safe "bag"

using Dasync.Collections;

string[] servers = new string[] { "10.17.100.1", "10.17.100.10", "10.17.100.20" };

ConcurrentBag<string> bag = new ConcurrentBag<string>();

await severs.ParallelForEachAsync<string>(async forServer =>
{
    string response = await this.Method1Async(forServer);

    bag.Add(response);

}, maxDegreeOfParallelism: 10); 

foreach(string forBagItem in bag)
{
    // evaluate the results
}

【讨论】:

    【解决方案2】:

    注意:感谢 Theodor Zoulias 提到这一点:

    根据this question Parallel.ForEach 不会等待任务完成,因此动作内部的awaiting 不会做任何事情,并且一旦所有任务启动,IsCompleted 将设置为 true。


    ForEach的Action的签名改为async以启用awaiting。

    using System.Threading.Tasks;
    using System.Diagnostics;
    
    private void Button_Click(object sender, RoutedEventArgs e)
    {
        string[] dummyArray = new string[] { "anyvalue" };
    
        Parallel.ForEach(dummyArray, async (forDummy) =>
        {
            await this.Method1Async();
        });
    
        Debug.WriteLine("Finished");
    }
    
    private async Task Method1Async()
    {
        await this.Method2Async();
    }
    
    private async Task Method2Async()
    {
        Debug.WriteLine("> Method2Async");
    
        string result = await Task<string>.Run(() =>
        {
            Debug.WriteLine("  Method2Async before return");
            return "anydata"; // this return sometimes does not "come back" ...
        });
    
        // ... so this code is never reached
        Debug.WriteLine("< Method2Async" + result);
    }
    

    通常,在编写异步代码时,您必须避免同步调用(Wait、Result 等),否则编写异步代码毫无意义。只需删除所有任务、异步和等待,您的代码就会运行得更快。

    此规则的一个例外是当您故意想要阻塞线程时,例如在遗留代码中。

    编辑:

    如果你想等待所有任务完成后再进行下一条语句,可以使用WhenAll

    private async void Button_Click(object sender, RoutedEventArgs e)
    {
        string[] dummyArray = new string[] { "anyvalue" };
    
        Task[] tasks = dummyArray.Select(async x => await Method1Async()).ToArray();
        await Task.WhenAll(tasks);
    }
    

    private async void Button_Click(object sender, RoutedEventArgs e)
    {
        string[] dummyArray = new string[] { "anyvalue" };
    
        Task[] tasks = dummyArray.Select(x => Method1Async()).ToArray();
        await Task.WhenAll(tasks);
    }
    

    编辑:

    如果你想限制并行任务的数量,那么你可以做this

        public async Task Button_Click()
        {
            string[] servers = new string[] { "1", "2", "3", "4", "5" };
    
            var maxParallel = 3;
            var throttler = new SemaphoreSlim(initialCount: maxParallel);
            var tasks = servers.Select(async server =>
            {
                try
                {
                    await throttler.WaitAsync();
                    await Method1Async(server);
                }
                finally
                {
                    throttler.Release();
                }
            });
            await Task.WhenAll(tasks);
    
            Console.WriteLine("Finished");
        }
    

    【讨论】:

    • @marsh-wiggle 您可以改为创建一个循环并检查ParallelLoopResult.IsCompleted,但这对于本示例来说太过分了。
    • @marsh-wiggle 您在Wanted Output 中说过您希望在第一个完成后调用第二个。对吗?
    • @marsh-wiggle 我添加了另一个更新。希望对你有帮助
    • Parallel.ForEachis not async-friendly。传递的 lambda 是 async void
    • @marsh-wiggle 请再次查看更新后的答案以及上面的评论。似乎并行 foreach 有一个限制。
    猜你喜欢
    • 2020-09-20
    • 1970-01-01
    • 2019-02-14
    • 1970-01-01
    • 2022-12-28
    • 2015-04-15
    • 2017-09-02
    • 1970-01-01
    相关资源
    最近更新 更多