【问题标题】:Using a BlockingCollection to queue Tasks使用 BlockingCollection 对任务进行排队
【发布时间】:2016-04-21 06:58:50
【问题描述】:

我正在尝试创建一种将要运行的任务排队的方法,因此我尝试使用BlockingCollection 来实现它。我发现的问题是每当我尝试添加Task 时,任务都会执行。示例代码如下:

private void button1_Click(object sender, EventArgs e)
{
    textBox2.Clear();
    for (int i = 0; i < 10; i++)
    _processCollection.Add(BigTask(i));
}

static BlockingCollection<Task> _processCollection = new BlockingCollection<Task>();
Thread ConsumerThread = new Thread(LaunchConsumer);

private static async void LaunchConsumer()
{
    while (true)
    {
        var processTask = _processCollection.Take();
        await Task.Run(() => processTask);
    }
}

async Task BigTask(int i)
{
    await Task.Delay(5000);
    textBox2.AppendText($"Text{i}\n");
}

似乎在调试中发生的所有任务似乎都在运行,因为它们被添加到阻塞集合中。我尝试将阻塞集合切换为使用Action,但这只会导致没有任何事情发生。如下(仅显示更改):

private void button1_Click(object sender, EventArgs e)
{
    textBox2.Clear();
    for (int i = 0; i < 10; i++)
    {
        int iC = i;
        _processCollection.Add(async () => await BigTask(iC));
    }
}

static BlockingCollection<Action> _processCollection = new BlockingCollection<Action>();
Thread ConsumerThread = new Thread(LaunchConsumer);

private static async void LaunchConsumer()
{
    while (true)
    {
        var processTask = _processCollection.Take();
        await Task.Run(processTask);
    }
}

我觉得我在某个地方犯了一些小错误,因为感觉这应该可行。我试图找人做类似的事情但没有运气,这让我觉得我的概念可能有缺陷,所以请随意提出替代方案。

【问题讨论】:

    标签: c# task-parallel-library blockingcollection


    【解决方案1】:

    _processCollection.Add(BigTask(i)); 不起作用,因为它会立即调用BigTask(i),当它被调用时,工作就开始了。

    通过将其包装在单独的 BigTask 启动器中,您走在了正确的轨道上,但是通过使用 Action,您不会为您的 LaunchConsumer 提供任何跟踪进度的方法。 await Task.Run(processTask) 将立即继续下一个任务。您需要使用Func&lt;Task&gt; 来避免这种情况。

    您没有看到任何结果的原因可能是无关的。现在您设法从新创建的线程启动任务,不再从 UI 线程调用 textBox2.AppendText。这是不支持的。只有 UI 线程可以访问 UI 对象。您可以使用textBox2.Invoke 将操作传递回UI 线程,然后该操作可以调用AppendText

    经过测试的工作代码:

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            ConsumerThread.Start();
        }
    
        private void button1_Click(object sender, EventArgs e)
        {
            textBox2.Clear();
            foreach (var i in Enumerable.Range(0, 10))
                _processCollection.Add(() => BigTask(i));
        }
    
        static BlockingCollection<Func<Task>> _processCollection = new BlockingCollection<Func<Task>>();
        Thread ConsumerThread = new Thread(LaunchConsumer);
    
        private static async void LaunchConsumer()
        {
            while (true)
            {
                var processTask = _processCollection.Take();
                await Task.Run(processTask);
            }
        }
    
        async Task BigTask(int i)
        {
            await Task.Delay(5000);
            textBox2.Invoke(new Action(() => textBox2.AppendText($"Text{i}\n")));
        }
    }
    

    也就是说,BlockingCollection 并不是在这里使用的最佳集合类型。它只将一个线程专用于等待。此外,Task.Run 当您已经在后台线程中时,诚然有时会很有用,但不会在此处添加任何内容。做什么取决于您的需求。是否事先知道所有任务会产生影响。您是否需要多个消费者会有所不同。其他我没有想到的事情也可能会有所作为。

    【讨论】:

    • 嗨,您能否澄清一下您对使用Func&lt;Task&gt; 的含义。我已经尝试在阻塞集合中使用它并且结果没有变化。语法是否与我使用Action时的语法相同。
    • 关于看不到结果,我的原始代码示例有非 UI 内容,所以我有理由确定它没有执行。无论如何,我在这个示例中调用了,但仍然没有显示结果。
    • 在这种情况下,您只需将Action 更改为Func&lt;Task&gt; 即可,无需进行任何其他更改。 async () =&gt; { } 可以转换为async void 委托或async Task 委托,编译器将根据调用者的期望确定哪个。我会尝试修改我的答案,以展示我以后能够测试的东西。
    • 这听起来像是我尝试过的。有兴趣查看您的示例代码。
    • 哈,我在查看我的代码时意识到了错误。我没有开始我的消费者线程(doh!)。非常感谢您发布完整的答案。出于兴趣,您对BlockingCollection 有什么建议。我正在寻求一些可以让我实现具有x 消费者数量的 FIFO 集合的东西。
    猜你喜欢
    • 1970-01-01
    • 2016-08-01
    • 1970-01-01
    • 2019-02-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-24
    • 1970-01-01
    相关资源
    最近更新 更多