24

CancellationTokenSource を使用して関数を提供し、ユーザーが長いアクションをキャンセルできるようにしました。ただし、ユーザーが最初のキャンセルを適用すると、その後のアクションは機能しなくなります。私の推測では、CancellationTokenSource のステータスが Cancel に設定されており、それを元に戻す方法を知りたいのです。

  • 質問 1: 初めて使用した後に CancellationTokenSource をリセットする方法を教えてください。

  • 質問 2: VS2010 でマルチスレッドをデバッグする方法は? アプリケーションをデバッグ モードで実行すると、次のステートメントの例外が表示されます。

    this.Text = string.Format("Processing {0} on thread {1}", filename, Thread.CurrentThread.ManagedThreadId);
    

InvalidOperaationException はユーザー コードによって処理されませんでした クロススレッド操作が無効です: コントロール 'MainForm' は、それが作成されたスレッド以外のスレッドからアクセスされました。

ありがとうございました。

private CancellationTokenSource cancelToken = new CancellationTokenSource();

private void button1_Click(object sender, EventArgs e)
{
    Task.Factory.StartNew( () =>
    {
        ProcessFilesThree();
    });
}

private void ProcessFilesThree()
{
    ParallelOptions parOpts = new ParallelOptions();
    parOpts.CancellationToken = cancelToken.Token;
    parOpts.MaxDegreeOfParallelism = System.Environment.ProcessorCount;

    string[] files = Directory.GetFiles(@"C:\temp\In", "*.jpg", SearchOption.AllDirectories);
    string newDir = @"C:\temp\Out\";
    Directory.CreateDirectory(newDir);

    try
    {
        Parallel.ForEach(files, parOpts, (currentFile) =>
        {
            parOpts.CancellationToken.ThrowIfCancellationRequested();

            string filename = Path.GetFileName(currentFile);

            using (Bitmap bitmap = new Bitmap(currentFile))
            {
                bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone);
                bitmap.Save(Path.Combine(newDir, filename));
                this.Text =  tring.Format("Processing {0} on thread {1}",  filename, Thread.CurrentThread.ManagedThreadId);
            }
        });

        this.Text = "All done!";
    }
    catch (OperationCanceledException ex)
    {
        this.Text = ex.Message;                             
    }
}

private void button2_Click(object sender, EventArgs e)
{
    cancelToken.Cancel();
}
4

5 に答える 5

46

質問 1> 初めて使用した後に CancellationTokenSource をリセットするにはどうすればよいですか?

キャンセルするとキャンセルされ、元に戻すことはできません。新しい が必要ですCancellationTokenSource。ACancellationTokenSourceはある種の工場ではありません。単一のトークンの所有者です。IMO と呼ばれるべきCancellationTokenOwnerでした。

質問 2> VS2010 でマルチスレッドをデバッグするにはどうすればよいですか? アプリケーションをデバッグ モードで実行すると、次のステートメントの例外が表示されます。

それはデバッグとは何の関係もありません。別のスレッドから GUI コントロールにアクセスすることはできません。そのために使用する必要がありますInvoke。リリースモードでは一部のチェックが無効になっているため、デバッグモードでのみ問題が発生すると思います。しかし、バグはまだ残っています。

Parallel.ForEach(files, parOpts, (currentFile) =>
{
  ...  
  this.Text =  ...;// <- this assignment is illegal
  ...
});
于 2011-05-29T15:21:29.520 に答える
2

Visual Studio の [Debug] > [windows] の下で、スレッド ウィンドウ、コールスタック ウィンドウ、および並列タスク ウィンドウを確認します。

例外が発生してデバッガーが中断した場合は、コールスタック ウィンドウを見て、どのスレッドが呼び出しを行っているか、およびそのスレッドがどこから来ているかを確認できます。

-投稿されたスクリーンショットに基づいて編集-

コールスタックを右クリックして「外部コードを表示」を選択すると、スタックで何が起こっているかを正確に確認できますが、「外部コード」は「フレームワークのどこか」を意味するため、役立つ場合とそうでない場合があります(通常は見つけます面白いけど:))

スクリーンショットから、呼び出しがスレッド プール スレッドから行われていることもわかります。スレッド ウィンドウを見ると、そのうちの 1 つに黄色の矢印が付いていることがわかります。それが現在実行中のスレッドであり、例外がスローされている場所です。このスレッドの名前は「ワーカー スレッド」で、これはスレッド プールからのものであることを意味します。

既に述べたように、ユーザー スレッドから UI を更新する必要があります。たとえば、このためにコントロールで「Invoke」を使用できます。@CodeInChaos awnser を参照してください。

-編集2-

@CodeInChaos awnser に関するコメントを読みましたが、TPL のような方法でそれを行う 1 つの方法を次に示します。まずTaskScheduler、UI スレッドでタスクを実行する a のインスタンスを取得する必要があります。TaskSchedulerこれを行うには、たとえば名前付きの ui-class でa を宣言しuiScheduler、コンストラクターで a を次のように設定します。TaskScheduler.FromCurrentSynchronizationContext();

これで、UI を更新する新しいタスクを作成できます。

 Task.Factory.StartNew( ()=> String.Format("Processing {0} on thread {1}", filename,Thread.CurrentThread.ManagedThreadId),
 CancellationToken.None,
 TaskCreationOptions.None,
 uiScheduler ); //passing in our uiScheduler here will cause this task to run on the ui thread

タスクを開始するときに、タスク スケジューラをタスクに渡すことに注意してください。

これを行うには、TaskContinuation API を使用する 2 つ目の方法もあります。ただし、Paralell.Foreach はもう使用できませんが、通常の foreach とタスクを使用します。重要なのは、タスクを使用すると、最初のタスクが完了すると実行される別のタスクをスケジュールできることです。しかし、2 番目のタスクは同じスケジューラーで実行する必要はありません。これは、バックグラウンドでいくつかの作業を行い、UI を更新する必要があるため、現時点では非常に便利です。

  foreach( var currectFile in files ) {
    Task.Factory.StartNew( cf => { 
      string filename = Path.GetFileName( cf ); //make suse you use cf here, otherwise you'll get a race condition
      using( Bitmap bitmap = new Bitmap( cf ) ) {// again use cf, not currentFile
        bitmap.RotateFlip( RotateFlipType.Rotate180FlipNone );
        bitmap.Save( Path.Combine( newDir, filename ) );
        return string.Format( "Processed {0} on thread {1}", filename, Thread.CurrentThread.ManagedThreadId );
      }
    }, currectFile, cancelToken.Token ) //we pass in currentFile to the task we're starting so that each task has their own 'currentFile' value
    .ContinueWith( t => this.Text = t.Result, //here we update the ui, now on the ui thread..
                   cancelToken.Token, 
                   TaskContinuationOptions.None, 
                   uiScheduler ); //..because we use the uiScheduler here
  }

ここで行っているのは、作業を実行してメッセージを生成する新しいタスクをループごとに作成し、実際に UI を更新する別のタスクにフックすることです。

ContinueWith と継続の詳細については、こちらをご覧ください。

于 2011-05-29T15:29:24.167 に答える
1

デバッグには、ParallelStacksウィンドウをThreadsウィンドウと組み合わせて使用​​することを強くお勧めします。並列スタックウィンドウを使用すると、すべてのスレッドのコールスタックを1つの結合されたディスプレイに表示できます。コールスタック内のスレッドとポイント間を簡単にジャンプできます。並列スタックとスレッドウィンドウは、[デバッグ]>[ウィンドウ]にあります。

また、デバッグに本当に役立つもう1つのことは、CLR例外がスローされたときと、ユーザーが処理していないときの両方で、CLR例外のスローをオンにすることです。これを行うには、[デバッグ]> [例外]に移動し、両方のオプションを有効にします-

例外ウィンドウ

于 2011-05-29T15:39:30.657 に答える