使用BlockingCollection对任务进行排队

时间:2016-04-21 06:58:51

标签: c# task-parallel-library blockingcollection

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

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

1 个答案:

答案 0 :(得分:2)

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

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

您没有看到任何结果的原因可能不相关。既然您已设法从新创建的线程启动任务,则不再从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当你已经在后台线程中时,有时可能会有用,但不会在这里添加任何内容。该怎么做取决于您的需求。是否事先知道所有任务都有所不同。您是否希望多个消费者有所作为。我没有想到的其他事情也可能有所不同。