【问题标题】:How do I stop awaiting a task but keep the task running in background?如何停止等待任务但保持任务在后台运行?
【发布时间】:2019-02-26 20:47:00
【问题描述】:

如果我有一个要一起执行的任务列表,但同时我想一起执行一定数量的任务,所以我等待其中一个,直到其中一个完成,这应该意味着等待应该停止并且应该允许开始一个新任务,但是当其中一个完成时,我不知道如何停止等待当前正在等待的任务,我不想取消任务,只是停止等待并让它继续在后台运行。

我有以下代码

foreach (var link in SharedVars.DownloadQueue)
{
    if (currentRunningCount != batch)
    {
        var task = DownloadFile(extraPathPerLink, link, totalLen);
        _ = task.ContinueWith(_ =>
        {
            downloadQueueLinksTasks.Remove(task);
            currentRunningCount--;
            // TODO SHOULD CHANGE WHAT IS AWAITED
        });
        currentRunningCount++;
        downloadQueueLinksTasks.Add(task);
    }

    if (currentRunningCount == batch)
    {
        // TODO SHOULD NOT AWAIT 0
        await downloadQueueLinksTasks[0];
    }
}

我发现了Task.WhenAny,但从this comment here 我了解到其他任务将被忽略,因此这不是我想要实现的解决方案。

如果问题是愚蠢或错误的,我很抱歉,但我似乎找不到任何有关如何解决它的信息,或者我想要实现的操作的名称是什么,所以我什至可以正确搜索。

解决方案编辑

提供的所有答案都是正确的,我接受了我决定使用的答案,但仍然所有答案都是正确的。

谢谢大家,我从你们所有人那里学到了很多,从这些不同的答案和解决问题的不同方法以及如何思考它。

我了解到这个具体问题是我仍然需要等待剩下的其他任务,所以解决方案是在循环中使用Task.WhenAny(它返回完成的任务(这也很重要))AND Task.WhenAll 在循环外等待其他剩下的任务。

【问题讨论】:

    标签: c# multithreading asynchronous async-await


    【解决方案1】:

    Task.WhenAny 返回已完成的任务。

    foreach (var link in SharedVars.DownloadQueue)
    {
        var task = DownloadFile(extraPathPerLink, link, totalLen);
        downloadQueueLinksTasks.Add(task);
    
        if (downloadQueueLinksTasks.Count == batch)
        {
            // Wait for any Task to complete, then remove it from
            // the list of pending tasks.
            var completedTask = await Task.WhenAny(downloadQueueLinksTasks);
            downloadQueueLinksTasks.Remove(completedTask);
        }
    }
    
    // Wait for all of the remaining Tasks to complete
    await Task.WhenAll(downloadQueueLinksTasks);
    

    【讨论】:

    • 但这不会让其他任务也批处理执行,例如,如果批处理设置为 5,我希望一次执行最多 5 个任务,但一旦其中一个任务完成,另一个应该开始,但不是所有其他人(其他人应该以同样的方式等待)
    • 是的,这就是这段代码的作用。无论是否正在等待,任务都在执行。此代码启动 5 个任务,然后等待其中一个任务完成。当一个任务完成时,它将它从downloadQueueLinksTasks 中删除,启动另一个任务,并等待下一个任务完成。试试看。
    • 天哪,解决方案有效,完全按照我的计划运作,非常感谢。
    【解决方案2】:

    您可以使用Task.WaitAny()

    这是行为的演示:

    public static async Task Main(string[] args)
    {
         IList<Task> tasks = new List<Task>();
    
        tasks.Add(TestAsync(0));
        tasks.Add(TestAsync(1));
        tasks.Add(TestAsync(2));
        tasks.Add(TestAsync(3));
        tasks.Add(TestAsync(4));
        tasks.Add(TestAsync(5));
    
        var result = Task.WaitAny(tasks.ToArray());
    
        Console.WriteLine("returned task id is {0}", result);
    
        ///do other operations where
    
        //before exiting wait for other tasks so that your tasks won't get cancellation signal
        await Task.WhenAll(tasks.ToArray());
    
    }
    
    public static async Task TestAsync(int i)
    {
        Console.WriteLine("Staring to wait" + i);
        await Task.Delay(new Random().Next(1000, 10000));
        Console.WriteLine("Task finished" + i);
    }
    

    输出:

    Staring to wait0
    Staring to wait1
    Staring to wait2
    Staring to wait3
    Staring to wait4
    Staring to wait5
    Task finished0
    returned task id is 0
    Task finished4
    Task finished2
    Task finished1
    Task finished5
    Task finished3
    

    【讨论】:

    • 当我在第一批执行后出于某种原因尝试使用它时,for循环退出,但是当我尝试逐行使用调试器时,它按预期工作(我可以在几次之后按继续行,它将继续完美地工作)。
    • 因为您正在终止您的应用程序。最终你需要等待你的任务。 .请检查我编辑的答案
    • 哦,对不起,我没有注意到那行,我根本没有包括它,我以为内部的 Task.WhenAny 会完成这项工作,但忘记了一些任务不会到达它,因为ifs 我有。
    【解决方案3】:

    您要问的是节流,对于异步代码,最好通过SemaphoreSlim 表示:

    var semaphore = new SemaphoreSlim(batch);
    var tasks = SharedVars.DownloadQueue.Select(link =>
    {
      await semaphore.WaitAsync();
      try { return DownloadFile(extraPathPerLink, link, totalLen); }
      finally { semaphore.Release(); }
    });
    var results = await Task.WhenAll(tasks);
    

    【讨论】:

      【解决方案4】:

      您应该使用 Microsoft 的响应式框架(又名 Rx)- NuGet System.Reactive 并添加 using System.Reactive.Linq; - 然后您可以这样做:

      IDisposable subscription =
          SharedVars.DownloadQueue
              .ToObservable()
              .Select(link =>
                  Observable.FromAsync(() => DownloadFile(extraPathPerLink, link, totalLen)))
              .Merge(batch) //max concurrent downloads
              .Subscribe(file =>
              {
                  /* process downloaded file here
                     (no longer a task) */
              });
      

      如果您需要在下载自然完成之前停止下载,请致电subscription.Dispose()

      【讨论】:

      • 这看起来很有希望,但我有一个问题,因为我找不到任何有关它的信息,Rx 是跨平台的吗?因为它依赖于 .Net Framework 而不是 .Net Core,所以我很困惑。
      猜你喜欢
      • 2021-04-09
      • 1970-01-01
      • 1970-01-01
      • 2017-01-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-22
      相关资源
      最近更新 更多