2016-04-21 86 views
2

我想创建一个排队任务运行的方法,所以我试图用BlockingCollection来实现它。我发现的问题是每当我尝试添加Task时,任务都会执行。示例代码如下:使用BlockingCollection排队任务

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

我觉得我在某处做了一些小的错误,因为它觉得这应该工作。我试图找人做类似的事情,但没有运气,这让我觉得也许我的观念有缺陷,所以随时提出一个替代方案。

回答

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可以承认有时会很有用,但不会在此添加任何内容。做什么取决于你的需求。事先知道所有任务是否有所不同。无论你是否想要多个消费者都会有所作为。其他我没有想到的事情也可能会有所作为。

+0

嗨,你能澄清一下你的意思吗?使用'Func '。我曾尝试在阻塞收集中使用它,结果没有任何变化。当使用'Action'时,语法仍然和我的相同。 –

+0

关于看不到的结果,我原来的代码示例没有UI的东西,所以我有理由相信它没有执行。无论如何,我在这个例子中都会调用,但仍然没有显示结果。 –

+0

在这种情况下,您可以将'Action'更改为'Func '而无需做任何其他更改。 'async()=> {}'可以转换为'async void'委托或者'async Task'委托,并且编译器会根据调用者的期望来决定。我会尝试修改我的答案,以显示我稍后可以测试的内容。 – hvd