2016-04-21 38 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