2011-08-04 3 views
3

TPLとWPfを初めて使用していて、次の問題があります。 無限ループ(ここではforループのみ)でサイトをダウンロードしようとしており、 をキューに追加しようとしています。次のタスクはそれを取り出し、それをTextblockに表示します。 しかし、私は適切なTaskSchedulerを使用すると思うが、私はUIのために正しいスレッドを取得していないようだ。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()+ 0x44のバイト mscorlib.dll!System.Threading.Tasks.Task.Execute()+ 0x43 bytes mscorlib.dll!System.Threading.Tasks.Task.ExecutionContextCallback(オブジェクトobj)+ 0x27バイト
mscorlib.dll!System.Threading.ExecutionContext .Run(System.Threading.ExecutionContext executionContext、System.Threading.ContextCallbackコールバック、オブジェクト状態、bool ignoreSyncCtx)+ 0xb0バイト
mscorlib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot)+ 0x154バイト
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!

[マネージド移行にネイティブ]バイト0x2d System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()+
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: 

ご協力いただきありがとうございます。 私はまだ2つの質問があります。 Task.Factory.StartNewでコードを少し書き直しました。しかし、私のTask2は問題を引き起こすようです。エラーメッセージはありません。むしろタイトなループのようです。もちろん、私は理由を理解できませんでしたか? あなたはとても親切で、私を正しい方向に向けるでしょうか。 私は6ヶ月間C#をやっていて、1週間はTPLしなければ、もう一度聞いてみません。しかし、この量の経験... もう一度ありがとう!

ブライアンコード:

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スレッドから開始されます。 すべての次のイベントで宣言されています: ます。private voidのButton1 Click(オブジェクト送信者、RoutedEventArgs e)の {} 私は上記のスタックを貼り付けました。 – user774326

+0

申し訳ありませんが、完全な例外の詳細(つまりexception.ToString())を貼り付けることはできますか?これは絶対に機能するはずです。ディスパッチャを呼び出す[Begin]呼び出しは必要ありません。また、作業がキャンセルされた場合、UI以外のスケジュールされたタスク(task1)にTextBlock2.Textを設定するバグがあります。 –

答えて

3

[OK]を、実際に私は再びあなたのコードを見て、問題は単純です:あなたは状態オブジェクトを取るコンストラクタのオーバーロードを使用して手動で新しいTaskインスタンスを構築しています。 TaskSchedulerが必要なコンストラクタオーバーロードはありません。

通常、人々はTask.Factory.StartNewを使用していますので、手動でTasksを構築していることに気が付きませんでした。 Taskインスタンスを手動で構築する場合、実行するスケジューラを指定する適切な方法は、Startオーバーロードを使用して、TaskSchedulerインスタンスを使用することです。だからあなたの場合には、あなたは何をする必要があります:今、あなたの更新されたコードに問題はあなたが効果的であるUI上Task(ディスパッチャー)スレッドをスケジュールしたさ

[OK]を

taskCP.Start(uiTaskScheduler); 

UPDATE、そこに座ってタイトなループで読む。

コードが修正されたので、ではなく、はUIスレッドでtask2をスケジュールします。そこから通知をUIにプッシュしたい場合は、Dispatcher::BeginInvokeを別の回答として提案するか、ループ内のuiTaskSchedulerを使用して新しいタスクを開始することができます。 Dispatcher :: BeginInvokeを呼び出すと、オーバーヘッドが少なくなり、コードが賢明なIMHOになります。そのようにすることをお勧めします。

+0

ありがとうございます。私は別のものを追加し、Task.Factory.StartNewでそれを書き換えた – user774326

+0

さて、私はそれを理解しています。ディスパッチャーとの組み合わせでタスクがどのように機能するかは、私の側ではむしろ誤解されていました。私のコードを改善し、より良いものになるために私が何をやって、練習しなければならないかを教えてくれてありがとう。 – user774326

+0

私は最初からここまで来る気がしましたが、あなたの本当の疑問は、あなたのタスクがUIスレッドで実行されていなかったことでした。本当にあなたのタスクに「正しい」答えを得るためには、質問。トヨを助けてうれしい! –

0

Dispatcher.Invoke()を使用して、異なるスレッドのUI要素で使用するコードを呼び出します。タスクは、UIスレッド以外の別のスレッドで実行されるため、DispatcherはどのあなたのUIスレッドにアクセスするためにあなたに変化を与える

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

:例

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

あなたの答えをありがとうが、私はTPLでもそれを行うことができます、それは上記のパターンに従うことを意味しますか? – user774326

+0

ありがとうございますが、その部分は完全に動作しますが、問題はTxtBlock3.Text = dlCodeの部分です – user774326

+0

'TextBoxl3.Text'のように別のスレッドのUI要素にアクセスするコードで' Dispatcher.Invoke'を使用してください –

2

コードにはいくつか問題があります。

  • TaskSchedulerを受け入れるTask ctorのオーバーロードはありません。あなたが実際に行ったことは、TaskSchedulerstateパラメータに渡し、ラムダ式の変数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(); 

ContinueWithTaskSchedulerを受け入れることができ、それは、私が上記の行っている正確に何であることに注意してください。私もtaskCPをデフォルトのスケジューラで実行していて、TxtBlock3にアクセスする前にDispatcher.Invokeを使用しています。

Taskを特定のスケジューラで開始する場合は、TaskSchedulerを次のようにStartメソッドに渡します。

task1.Start(TaskScheduler.Default); 
+0

ブライアンありがとうございます。私も上記の別の質問を追加しました – user774326

+0

私のコードの 'obj'はタイプミスでした。私はそれを削除した。 –

関連する問題