【问题标题】:C# tasks with dynamic delay具有动态延迟的 C# 任务
【发布时间】:2013-10-21 03:48:09
【问题描述】:

我有一个函数需要一次处理 3 个项目,如果总时间少于 x 秒,线程应该在继续处理之前休眠剩余的秒数。

所以我正在做以下事情:

    private void ProcessItems()
    {
        for (int i = 0, n = items.Count; i < n; i++)
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            batch.Add(items[i]);

            if (batch.Count == 3 || i >= items.Count - 3)
            {
                List<Task> tasks = new List<Task>(3);

                foreach (Item item in batch)
                    tasks.Add(Task.Factory.StartNew(() => ProcessItem(item)));

                Task.WaitAll(tasks.ToArray());

                batch.Clear();
            }

            stopwatch.Stop();

            int elapsed = (int)stopwatch.ElapsedMilliseconds;
            int delay = (3000) - elapsed;

            if (delay > 0)
                Thread.Sleep(delay);
        }
    }

ProcessItem 函数发出一个网络请求并处理响应(回调)。这是一个需要少量时间的功能。

但是,如果我正确理解任务,一个线程可以有多个任务。因此,如果我让线程休眠,其他任务可能会受到影响。

是否有更高效的方式来实现上述目标,是否可以在 Parallel.Foreach 中使用任务?

【问题讨论】:

  • 任何包含完整源代码的最终解决方案?

标签: c# parallel-processing task async-await


【解决方案1】:

使用

Task.Delay 

改为

    static async Task DoSomeProcess()
    {
        await Task.Delay(3000);
    }

你说得对,Thread.Sleep 会阻塞其他任务

是的,您可以将 async/await 模式与 Parallel 配对。

【讨论】:

    【解决方案2】:

    任务在自动管理的线程上运行。阻塞线程本质上没有错。就是有点浪费。

    以下是我将如何非常干净地实现它:

    MyItem[] items = ...;
    foreach(MyItem[] itemsChunk in items.AsChunked(3)) {
     Parallel.ForEach(itemsChunk, item => Process(item));
     //here you can insert a delay
    }
    

    这不会浪费一个线程,而且非常简单。 Parallel.ForEach 也使用当前线程来处理工作项,因此它不会闲置。您也可以添加延迟逻辑。实现AsChunked 留给读者作为练习......这个函数应该将列表拆分为给定大小的块(3)。这种辅助函数的好处是它可以将批处理逻辑与重要部分分开。

    【讨论】:

    • 是的。如果您不想为此刻录线程,请使用await Task.Delay。但是,这会强制您使用整个异步模型。您还必须更改所有调用者以使用 Task。如果您的线程很少,那么从性能的角度来看,您选择哪种模型并不重要,因为线程具有恒定的开销。与异步相比,它不会使在其上运行的东西变慢或变快。如果你不需要异步,我建议不要使用它,因为它会使事情复杂化。
    • 那么 await Task.Delay 如何适合您的示例?
    • 您在标记的位置插入await Task.Delay(3000),标记方法async,将返回类型更改为Task,现在必须更改所有调用者以等待返回的任务。我认为你应该研究一下异步是如何工作的,然后决定你是否需要它。可能不是。您是否担心某个具体问题?
    • 那么 await Task.Delay(3000) 返回一个等待 3000 的空任务呢?
    • @Ivan-MarkDebono 不,Task.Delay() 返回一个等待给定时间的Taskawait Task.Delay() 异步等待该时间。
    【解决方案3】:

    您的ProcessItems 方法可以很容易地转换为异步版本ProcessItemsAsync(我没有验证“批处理”逻辑):

    private async Task ProcessItemsAsync()
    {
        for (int i = 0, n = items.Count; i < n; i++)
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
    
            batch.Add(items[i]);
    
            if (batch.Count == 3 || i >= items.Count - 3)
            {
                List<Task> tasks = new List<Task>(3);
    
                foreach (Item item in batch)
                    tasks.Add(Task.Run(() => ProcessItem(item)));
    
                await Task.WhenAll(tasks.ToArray());
    
                batch.Clear();
            }
    
            stopwatch.Stop();
    
            int elapsed = (int)stopwatch.ElapsedMilliseconds;
            int delay = (3000) - elapsed;
    
            if (delay > 0)
                await Task.Delay(delay);
        }
    }
    

    正如@usr 在他的回答中指出的那样,唯一的好处是您不会用Task.WaitAll()Thread.Sleep() 阻塞ProcessItems 线程。是采用这种方法还是Parallel.ForEach 可能取决于代码的运行环境。 Async/await 不会让您的代码运行得更快,但会提高其在服务器端执行的可扩展性,因为它可能需要更少的线程来运行,因此可以为更多的客户端提供服务。

    还要注意,现在ProcessItemsAsync 本身就是一个异步任务,所以为了保持调用它的代码流不变,你需要这样调用它:

    ProcessItemsAsync().Wait();
    

    这本身就是一个阻塞调用,可能会扼杀我们刚刚获得的async 的优势。能否完全消除应用中的此类障碍,很大程度上取决于应用的其他工作流程。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-01-21
      • 2019-02-20
      • 2018-10-21
      • 2016-08-27
      • 1970-01-01
      • 2015-09-14
      • 1970-01-01
      • 2018-03-06
      相关资源
      最近更新 更多