2011-08-04 55 views
3

我是TPL和WPf的新手,並且存在以下問題。 我嘗試下載一個無限循環的站點(這裏只有一個for循環),並且 將它添加到隊列中。下一個任務將其取出並顯示在文本塊中。 但是我似乎沒有得到正確的線程爲用戶界面,但我認爲我正確使用TaskScheduler。WPF,TPL,生產者/消費者模式 - 錯誤線程錯誤

謝謝你的幫助!

BlockingCollection<string> blockingCollection = new BlockingCollection<string>(); 
CancellationToken token = tokenSource.Token; 
TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); 

     Task task1 = new Task(
      (obj) => 
      { 
       for (int i = 0; i < 10; i++) 
       { 
        if (token.IsCancellationRequested) 
        { 
         TxtBlock2.Text = "Task cancel detected"; 
         throw new OperationCanceledException(token); 
        } 
        else 
        { 
         string code = i.ToString() + "\t" + AsyncHttpReq.get_source_WebRequest(uri); 
         blockingCollection.Add(code); 
        } 
       } 
      }, TaskScheduler.Default); 


     task1.ContinueWith(antecedents => 
     { 
      TxtBlock2.Text = "Signalling production end"; 
      blockingCollection.CompleteAdding(); 
     }, uiScheduler); 


     Task taskCP = new Task(
      (obj) => 
      { 
       while (!blockingCollection.IsCompleted) 
       { 
        string dlCode; 
        if (blockingCollection.TryTake(out dlCode)) 
        { 
    //the calling thread cannot access this object because a different thread owns it. 
         TxtBlock3.Text = dlCode; 
        } 
       } 
      }, uiScheduler); 


WindowsBase.dll!System.Windows.Threading.Dispatcher.VerifyAccess() + 0x4a bytes 
WindowsBase.dll!System.Windows.DependencyObject.SetValue(System.Windows.DependencyProperty dp, object value) + 0x19 bytes 
PresentationFramework.dll!System.Windows.Controls.TextBlock.Text.set(string value) + 0x24 bytes 

WpfRibbonApplication4.exe!WpfRibbonApplication4.MainWindow.Button1_Click.AnonymousMethod__4(obj對象)線83個+ 0x16字節C# mscorlib.dll中!System.Threading.Tasks.Task.InnerInvoke()+ 0×44字節 mscorlib.dll!System.Threading.Tasks.Task.Execute()+ 0x43 bytes mscorlib.dll!System.Threading.Tasks.Task.ExecutionContextCallback(object obj)+ 0x27 bytes
mscorlib.dll!System.Threading.ExecutionContext .Run(System.Threading.ExecutionContext executionContext,System.Threading.ContextCallback回調,對象狀態,布爾ignoreSyncCtx)+ 0xb0字節
mscorlib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot)+ 0x154 bytes
mscorlib.dll!System.Threading.Tasks.Task.ExecuteEntry(bool bPreventDoubleExecution)+ 0x8b字節
mscorlib.dll中!System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()+ 0x7的字節 mscorlib.dll中!System.Threading.ThreadPoolWorkQueue.Dispatch()+ 0x147字節
mscorlib.dll中! System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()+ 0x2d字節
[原產於託管過渡]

System.InvalidOperationException was unhandled by user code 
    Message=The calling thread cannot access this object because a different thread owns it. 
    Source=WindowsBase 
    StackTrace: 
     at System.Windows.Threading.Dispatcher.VerifyAccess() 
     at System.Windows.DependencyObject.SetValue(DependencyProperty dp, Object value) 
     at System.Windows.Controls.TextBlock.set_Text(String value) 
     at WpfRibbonApplication4.MainWindow.<>c__DisplayClass5.<Button1_Click>b__3(Object o) in C:\ ... \WpfRibbonApplication4\WpfRibbonApplication4\MainWindow.xaml.cs:line 90 
     at System.Threading.Tasks.Task.InnerInvoke() 
     at System.Threading.Tasks.Task.Execute() 
    InnerException: 

非常感謝您的幫助。 我仍然有兩個問題: 我用Task.Factory.StartNew重寫了我的代碼。但是,我的任務2似乎會導致問題。沒有錯誤信息。看起來像一個緊密的循環。當然,我沒有弄清楚爲什麼? 你會如此善良,並再次指向正確的方向。 請記住,我一直在做C#約6個月和TPL一個星期,否則我不會再問你。但有了這樣的經驗... 再次感謝你!

Brians代碼:

var task1 = new Task( 
    (obj) => 

爲什麼obj需要?

private void Button1_Click(object sender, RoutedEventArgs e) 
     { 

的TaskScheduler uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext(); BlockingCollection blockingCollection = new BlockingCollection(); CancellationTokenSource cts = new CancellationTokenSource();

  CancellationToken token = cts.Token; 

      Task task1 = Task.Factory.StartNew(
       () => 
       { 
        for (int i = 0; i < 10 ; i++) 
        { 
         token.ThrowIfCancellationRequested(); 
         string code = i++.ToString() + "\t" + AsyncHttpReq.get_source_WebRequest(uriDE); 
         blockingCollection.Add(code); 
        } 
       }, token, TaskCreationOptions.None, TaskScheduler.Default); 

      task1.ContinueWith(
       (antecedents) => 
       { 
        if (token.IsCancellationRequested) 
        { 
         TxtBlock2.Text = "Task cancel detected"; 
        } 
        else 
        { 
         TxtBlock2.Text = "Signalling production end"; 
        } 

        blockingCollection.CompleteAdding(); 

       }, uiTaskScheduler); 


      Task task2 = Task.Factory.StartNew(
       () => 
       { 
        while (!blockingCollection.IsCompleted) 
        { 
         string dlcode; 

         if (blockingCollection.TryTake(out dlcode)) 
         { 
          TxtBlock3.Text = dlcode; 
         } 
        } 

       }, token, TaskCreationOptions.None, uiTaskScheduler); 

     } 
+0

你能粘貼完整的堆棧跟蹤嗎?另外,最初是從UI線程調用的方法是否啓動? –

+0

是的,實際上它們是從ui線程開始的。 這一切都在以下事件宣稱: 私人無效的button1_Click(對象發件人,RoutedEventArgs E) {} 我粘貼上述堆棧。 – user774326

+0

對不起,你能粘貼完整的異常細節(即exception.ToString())嗎?這應該絕對有效。調用調度程序[開始]調用不是必需的。此外,如果工作取消,則會出現一個錯誤,您將在非UI計劃任務(task1)中設置TextBlock2.Text。 –

回答

3

好吧,其實我只是看着你的代碼再次,問題很簡單:你正在使用的構造函數重載需要一個狀態對象手動構建新Task實例。沒有構造函數超載,需要TaskScheduler

通常人們使用Task.Factory.StartNew,所以我甚至沒有注意到您正在手動構建Tasks。當您手動構建Task實例時,指定調度程序運行它們的正確方法是使用帶有TaskScheduler實例的Start超載。所以你的情況,你需要做的:

taskCP.Start(uiTaskScheduler); 

UPDATE

好了,所以現在的問題,並提供更新的代碼你有效地安排了Task在UI(調度)線程是坐在那裏讀緊閉的循環。

現在代碼被重寫了,很明顯你做而不是想要在UI線程上調度task2。如果您想要將通知從此處推送到用戶界面,則可以按照其他答案的建議呼叫Dispatcher::BeginInvoke,也可以使用循環內部的uiTaskScheduler啓動新任務。調用Dispatcher :: BeginInvoke將有更少的開銷和更清晰的代碼恕我直言,所以我建議只是這樣做。

+0

謝謝。我添加了另一個,並用Task.Factory重寫了它.StartNew – user774326

+0

好了,現在我明白了。這對我而言是一個誤解,表明它本身如何與調度員一起工作。感謝您幫助我改進我的代碼,並向我展示我必須努力和實踐的內容,才能變得更好。 – user774326

+0

我有一種感覺,我們可能會從一開始就在這裏結束,但真正的問題是爲什麼你的任務沒有在UI線程上執行,我真的很想找到底部,以獲得「正確」的答案給你題。很高興幫助壽! –

0

使用Dispatcher.Invoke()來調用在不同線程的UI元素上使用的代碼。例如

string dlCode; 
if (blockingCollection.TryTake(out dlCode)) 
{  
    Dispatcher.Invoke(() => 
    { 
     TxtBlock3.Text = dlCode; 
    } 
} 
+0

謝謝你的回答,但我也可以用TPL來做,這意味着遵循上面的模式? – user774326

+0

謝謝,但該部分完美工作,但問題是TxtBlock3.Text = dlCode – user774326

+0

的部分在代碼中使用Dispatcher.Invoke,從另一個線程訪問UI元素,就像在你的'TextBoxl3.Text' –

2

您可以使用調度程序來訪問UI線程,如:

Dispatcher.BeginInvoke(new Action(()=>{TxtBlock2.Text = "Signalling production end";})); 

因爲任務不是UI線程之外的另一個線程中運行,調度員給你一個改變訪問線程,你的UI在。 MSDN給出了一個很好的解釋,請查看備註部分: http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher.aspx

希望它有幫助。

+0

調度程序代碼工作得像魅力:)。謝謝 我仍然不明白爲什麼我的TPL代碼不起作用。 – user774326

2

您的代碼有幾個問題。

  • 接受TaskScheduler的任務ctor沒有超載。實際上,您將TaskScheduler傳遞給參數state,然後在lambda表達式的obj變量中選擇該參數。
  • 由於上述要點taskCP實際上是在默認調度程序上運行,而不是在uiScheduler上運行。
  • 由於上述要點taskCP正試圖通過修改TxtBlock3從非UI線程訪問UI元素。
  • 同樣task1正在通過修改TxtBlock2來嘗試。

下面是我將如何重構代碼。

var queue = new BlockingCollection<string>(); 
var cts = new CancellationTokenSource(); 
TaskScheduler ui = TaskScheduler.FromCurrentSynchronizationContext(); 

var task1 = new Task(
() => 
    { 
    for (int i = 0; i < 10; i++) 
    { 
     token.ThrowIfCancellationRequested(); 
     string code = i.ToString() + "\t" + AsyncHttpReq.get_source_WebRequest(uri); 
     queue.Add(code); 
    } 
    }); 

    task1.ContinueWith(
    antecedents => 
    { 
     if (token.IsCancellationRequested) 
     { 
     TxtBlock2.Text = "Task cancel detected"; 
     } 
     else 
     { 
     TxtBlock2.Text = "Signalling production end"; 
     } 
     queue.CompleteAdding(); 
    }, ui); 


    var taskCP = new Task(
    () => 
    { 
     while (!queue.IsCompleted) 
     { 
     string dlCode; 
     if (queue.TryTake(out dlCode)) 
     { 
      Dispatcher.Invoke(() => 
      { 
      TxtBlock3.Text = dlCode; 
      } 
     } 
     } 
    }); 

    task1.Start(); 
    taskCP.Start(); 

注意ContinueWith可以接受TaskScheduler那正是我在上面所做的。我還有taskCP在默認調度程序上運行,然後在訪問TxtBlock3之前使用Dispatcher.Invoke

如果您確實想在特定的調度程序上啓動Task,請將TaskScheduler傳遞給Start方法,如下所示。

task1.Start(TaskScheduler.Default); 
+0

謝謝Brian。我還在 – user774326

+0

上面添加了另一個問題我的代碼中的'obj'是一個錯字。我刪除它。 –