【问题标题】:Effects of async within a parallel for loop并行 for 循环中异步的影响
【发布时间】:2019-07-31 09:38:37
【问题描述】:

我首先要说的是,我目前正在学习多线程,所以可能不是我说的所有内容都是正确的 - 请随时根据需要纠正我。我确实对 async 和 await 有一个合理的理解。

我的基本目标如下: 我有一段代码,目前大约需要 3 秒。我试图在方法开始时加载一些数据,这些数据将在最后使用。我的计划是一开始就在不同的线程上加载数据——让其余代码独立执行。然后,在我需要数据的时候,如果没有加载数据,代码将等待。到目前为止,这一切似乎都运行良好,正如我所描述的那样。

我的问题与当我在并行 for 循环中调用异步方法而不等待它时会发生什么有关。

我的代码遵循这个结构:

public void MainCaller()
    {
        List<int> listFromThread = null;
        var secondThread = Task.Factory.StartNew(() =>
        {
            listFromThread = GetAllLists().Result;
        });

        //Do some other stuff

        secondThread.Wait();
        //Do not pass this point until this thread has completed
    }

    public Task<List<int>> GetAllLists()
    {
        var intList = new List<int>(){ /*Whatever... */};
        var returnList = new List<int>();
        Parallel.ForEach(intList, intEntry =>
        {
            var res = MyMethod().Result;
            returnList.AddRange(res);
        });

        return Task.FromResult(returnList);
    }

    private async Task<List<int>> MyMethod()
    {
        var myList = await obtainList.ToListAsync();
    }

注意 Parallel for Loop 调用 async 方法,但不等待它,因为它本身不是异步的。

这是一个在别处使用的方法,所以它是异步的是有效的。我知道一种选择是复制这个非异步的方法,但我想了解这里会发生什么。

我的问题是,我能否确定当我到达secondThread.Wait(); 时,执行的异步部分将完成。例如,将等待知道等待异步部分完成,或者异步会扰乱等待,还是会无缝地协同工作?

在我看来,可能由于未等待对 MyMethod 的调用,但在 MyMethod 中有等待,并行 for 循环可以在等待的调用完成之前继续执行吗?

那么我认为,因为它是通过引用分配它,所以一旦分配发生,该值将是正确的结果。

这让我认为只要等待知道等待异步完成,那么就没有问题 - 因此我的问题。

我猜这与我对 Tasks 缺乏了解有关?

我希望这很清楚?

【问题讨论】:

  • 我的看法是,整个拱门都搞砸了。首先,消除.Wait,然后消除.Results,然后消除Parallel.ForEach...我觉得这里不需要它们。现在,您应该真正了解异步和并行之间的区别了。
  • @AgentFire 你能解释一下为什么我应该删除所有这些东西吗?
  • 一般来说,您不应该在Tasks 上使用.Result 和/或.Wait,因为这会导致阻塞调用,从而破坏异步/等待的整个目的。每当您使用.Wait.Result 时,请考虑如何以不同的方式进行操作。如果您仅通过Task.FromResult 创建它并且不使用任何awaits,则还应避免返回一些Task,因为它只会给您带来更差的性能,并且与仅将其正常返回而不将其包装到任务中相比没有任何好处。
  • @Joelius 我不是想利用 await 的好处 - 我只是想了解我在问题中提出的要点
  • 实际上,在您的 parallel.for 循环中,您正在同步调用 MyMethod,因为您正在调用 'MyMethod().Result。所以从这个循环开始的每个任务都会阻塞,直到 MyMethod 完成。

标签: c# multithreading asynchronous task


【解决方案1】:

在您的代码中没有异步执行的部分。

  • 在 MainCaller 中,您启动一​​个任务并立即等待它完成。 这是一个阻塞操作,它只会引入额外的调用开销 GetAllLists 在另一个任务中。
  • 在此任务中,您调用了您启动了一个新任务(通过调用 GettAllLists)但立即 通过等待它的 Result 来等待这个任务完成(这也是阻塞的)。
  • 在 GetAllLists 启动的任务中,您有 Parallel.Foreach 循环,该循环启动 几个新任务。这些“for”任务中的每一个都将通过调用启动另一个任务 MyMethod 并立即等待其结果。

最终结果是您的代码完全同步执行。 Parallel.For 循环中引入了唯一的并行性。

提示:关于此主题的有用线程:Using async/await for multiple tasks

另外,您的代码包含一个严重的错误: Parallel.For 循环创建的每个任务最终都会通过调用 AddRange 将其部分列表添加到 ReturnList。 'AddRange' 不是线程安全的,因此您需要有一些同步机制(例如 'Lock'),否则您的 ReturnList 可能会损坏或不包含所有结果。另见:Is the List<T>.AddRange() thread safe?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-05-19
    • 1970-01-01
    • 2019-02-15
    • 2014-02-06
    • 1970-01-01
    • 1970-01-01
    • 2015-04-11
    • 1970-01-01
    相关资源
    最近更新 更多