使用 BlockingCollection 对任务进行排队

Using a BlockingCollection to queue Tasks

我正在尝试创建一种将任务排队到 运行 的方法,因此我尝试使用 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);
    }
}

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

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

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

您看不到任何结果的原因可能与此无关。现在您已设法从新创建的线程启动任务,对 textBox2.AppendText 的调用不再从 UI 线程完成。那是不支持的。只有 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 当您已经在后台线程中时,有时确实很有用,但不会在此处添加任何内容。该怎么做取决于您的需要。是否事先知道所有任务会有所不同。您是否需要多个消费者会有所不同。我没有想到的其他事情也可能有所不同。