使用 BlockingCollection 对任务进行排队 [英] Using a BlockingCollection to queue Tasks

查看:31
本文介绍了使用 BlockingCollection 对任务进行排队的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am trying to create a way to queue up Tasks to run, so I have tried to implement it using a BlockingCollection. The problem I find is whenever I try to add the Task, the Task executes. Sample code as below:

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,但这只会导致什么也没有发生.如下(仅显示更改):

What seems to happen in debug is all the tasks seem to run as they are added into the blocking collection. I tried switching the blocking collection to use Action, but that just leads to nothing happening. As below (only changes shown):

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);
    }
}

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

I feel like I have made some small error somewhere, because it feels like this should work. I have tried to find someone doing something similar but have had no luck, which makes me think maybe my concept is flawed so feel free to suggest an alternative.

推荐答案

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

_processCollection.Add(BigTask(i)); doesn't work because this calls BigTask(i) immediately, and when that is called, the work starts.

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

You were on the right track by wrapping this in a separate BigTask launcher, but by using Action, you don't provide your LaunchConsumer with any means to track the progress. await Task.Run(processTask) will continue pretty much immediately with the next task. You need to use Func<Task> to avoid that.

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

The reason you don't see any results is likely unrelated. Now that you manage to launch the task from your newly created thread, the call to textBox2.AppendText is no longer done from the UI thread. That's not supported. Only the UI thread can access UI objects. You can use textBox2.Invoke to pass an action back to the UI thread, and that action can then call 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 诚然有时很有用,但不会在此处添加任何内容.该怎么做取决于您的需求.是否事先知道所有任务会有所不同.您是否需要多个消费者会有所不同.其他我没有想到的事情也可能有所作为.


That said, BlockingCollection is not really the best collection type to use here. It dedicates one thread to pretty much nothing but waiting. Also, Task.Run when you're already in a background thread can admittedly sometimes be useful, but doesn't add anything here. What to do instead depends on your needs. Whether all tasks are known beforehand makes a difference. Whether you may want multiple consumers makes a difference. Other things I haven't thought of may also make a difference.

这篇关于使用 BlockingCollection 对任务进行排队的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆