CancellationTokenSourceを使用して関数を提供し、ユーザーが長いアクションをキャンセルできるようにしました。ただし、ユーザーが最初のキャンセルを適用すると、それ以降のアクションは機能しなくなります。私の推測では、CancellationTokenSourceのステータスがキャンセルに設定されており、リセットする方法を知りたいと思います。
質問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();
}
質問1>初めて使用した後にCancellationTokenSourceをリセットするにはどうすればよいですか?
キャンセルするとキャンセルされ、元に戻すことはできません。新しいCancellationTokenSource
が必要です。 CancellationTokenSource
はある種の工場ではありません。単一のトークンの所有者にすぎません。 IMOはCancellationTokenOwner
と呼ばれるべきでした。
質問2> VS2010でマルチスレッドをデバッグする方法は?アプリケーションをデバッグモードで実行すると、ステートメントに対して次の例外が表示されます。
それはデバッグとは何の関係もありません。別のスレッドからGUIコントロールにアクセスすることはできません。そのためにはInvoke
を使用する必要があります。リリースモードでは一部のチェックが無効になっているため、デバッグモードでのみ問題が発生すると思います。しかし、バグはまだあります。
Parallel.ForEach(files, parOpts, (currentFile) =>
{
...
this.Text = ...;// <- this assignment is illegal
...
});
VisualStudioの[デバッグ]> [ウィンドウ]で、スレッドウィンドウ、コールスタックウィンドウ、および並列タスクウィンドウを確認します。
発生した例外のためにデバッガーが中断した場合、コールスタックウィンドウを見て、どのスレッドが呼び出しを行っているか、およびそのスレッドがどこから来ているかを確認できます。
-投稿されたスクリーンショットに基づいて編集-
呼び出しスタックを右クリックして[外部コードを表示]を選択すると、スタックで何が起こっているかを正確に確認できますが、「外部コード」は「フレームワークのどこか」を意味するため、役立つ場合と役に立たない場合があります(通常はでも面白いです:))
スクリーンショットから、呼び出しがスレッドプールスレッドから行われていることもわかります。スレッドウィンドウを見ると、そのうちの1つに黄色の矢印が付いていることがわかります。これが、現在実行しているスレッドであり、例外がスローされている場所です。このスレッドの名前は「WorkerThread」であり、これはスレッドプールからのものであることを意味します。
すでに述べたように、ユーザースレッドからUIを更新する必要があります。たとえば、このためのコントロールで「Invoke」を使用できます。@ CodeInChaosawnserを参照してください。
-編集2-
@CodeInChaos awnserに関するコメントを読みましたが、TPLのような方法でそれを行う1つの方法があります。まず、UIでタスクを実行するTaskScheduler
のインスタンスを取得する必要があります。糸。これを行うには、たとえばTaskScheduler
という名前のuiクラスでuiScheduler
を宣言し、コンストラクターでそれを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
タスクを開始するときに、タスクスケジューラをタスクに渡すことに注意してください。
これを行う2つ目の方法もあります。これは、TaskContinuationAPIを使用します。ただし、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と継続についてもっと読むことができます ここ
デバッグには、[並列スタック]ウィンドウを[スレッド]ウィンドウと組み合わせて使用することを強くお勧めします。並列スタックウィンドウを使用すると、すべてのスレッドのコールスタックを1つの結合されたディスプレイに表示できます。コールスタック内のスレッドとポイント間を簡単にジャンプできます。並列スタックとスレッドウィンドウは、[デバッグ]> [ウィンドウ]にあります。
また、デバッグに本当に役立つもう1つのことは、CLR例外がスローされたときとユーザーが処理しないときの両方で、CLR例外のスローをオンにすることです。これを行うには、[デバッグ]> [例外]に移動し、両方のオプションを有効にします-
私はCancellationTokenSourceを醜い方法でだましているクラスを使用しています:
//.ctor
{
...
registerCancellationToken();
}
public CancellationTokenSource MyCancellationTokenSource
{
get;
private set;
}
void registerCancellationToken() {
MyCancellationTokenSource= new CancellationTokenSource();
MyCancellationTokenSource.Token.Register(() => {
MyCancellationTokenSource.Dispose();
registerCancellationToken();
});
}
// Use it this way:
MyCancellationTokenSource.Cancel();
それは醜い地獄を持っていますが、それは動作します。私は最終的にはより良い解決策を見つけなければなりません。
上記のスレッド化にご協力いただきありがとうございます。それは私の研究に役立ちました。私はこれを理解しようと多くの時間を費やしましたが、それは簡単ではありませんでした。友達と話すことも大いに役立ちました。
スレッドを開始および停止するときは、スレッドセーフな方法で実行する必要があります。また、スレッドを停止した後、スレッドを再開できる必要があります。この例では、WebアプリケーションでVS2010を使用しました。とにかくここに最初のhtmlがあります。その下には、最初にvb.netで、次にC#で背後にあるコードがあります。 C#バージョンは翻訳であることに注意してください。
最初のhtml:
<%@ Page Language="vb" AutoEventWireup="false" CodeBehind="Directory4.aspx.vb" Inherits="Thread_System.Directory4" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
<div>
<asp:Button ID="btn_Start" runat="server" Text="Start" />
<asp:Button ID="btn_Stop" runat="server" Text="Stop" />
<br />
<asp:Label ID="lblMessages" runat="server"></asp:Label>
<asp:Timer ID="Timer1" runat="server" Enabled="False" Interval="3000">
</asp:Timer>
<br />
</div>
</form>
</body>
</html>
次はvb.netです:
Imports System
Imports System.Web
Imports System.Threading.Tasks
Imports System.Threading
Public Class Directory4
Inherits System.Web.UI.Page
Private Shared cts As CancellationTokenSource = Nothing
Private Shared LockObj As New Object
Private Shared SillyValue As Integer = 0
Private Shared bInterrupted As Boolean = False
Private Shared bAllDone As Boolean = False
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
End Sub
Protected Sub DoStatusMessage(ByVal Msg As String)
Me.lblMessages.Text = Msg
Debug.Print(Msg)
End Sub
Protected Sub btn_Start_Click(sender As Object, e As EventArgs) Handles btn_Start.Click
If Not IsNothing(CTS) Then
If Not cts.IsCancellationRequested Then
DoStatusMessage("Please cancel the running process first.")
Exit Sub
End If
cts.Dispose()
cts = Nothing
DoStatusMessage("Plase cancel the running process or wait for it to complete.")
End If
bInterrupted = False
bAllDone = False
Dim ncts As New CancellationTokenSource
cts = ncts
' Pass the token to the cancelable operation.
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf DoSomeWork), cts.Token)
DoStatusMessage("This Task has now started.")
Timer1.Interval = 1000
Timer1.Enabled = True
End Sub
Protected Sub StopThread()
If IsNothing(cts) Then Exit Sub
SyncLock (LockObj)
cts.Cancel()
System.Threading.Thread.SpinWait(1)
cts.Dispose()
cts = Nothing
bAllDone = True
End SyncLock
End Sub
Protected Sub btn_Stop_Click(sender As Object, e As EventArgs) Handles btn_Stop.Click
If bAllDone Then
DoStatusMessage("Nothing running. Start the task if you like.")
Exit Sub
End If
bInterrupted = True
btn_Start.Enabled = True
StopThread()
DoStatusMessage("This Canceled Task has now been gently terminated.")
End Sub
Sub Refresh_Parent_Webpage_and_Exit()
'***** This refreshes the parent page.
Dim csname1 As [String] = "Exit_from_Dir4"
Dim cstype As Type = [GetType]()
' Get a ClientScriptManager reference from the Page class.
Dim cs As ClientScriptManager = Page.ClientScript
' Check to see if the startup script is already registered.
If Not cs.IsStartupScriptRegistered(cstype, csname1) Then
Dim cstext1 As New StringBuilder()
cstext1.Append("<script language=javascript>window.close();</script>")
cs.RegisterStartupScript(cstype, csname1, cstext1.ToString())
End If
End Sub
'Thread 2: The worker
Shared Sub DoSomeWork(ByVal token As CancellationToken)
Dim i As Integer
If IsNothing(token) Then
Debug.Print("Empty cancellation token passed.")
Exit Sub
End If
SyncLock (LockObj)
SillyValue = 0
End SyncLock
'Dim token As CancellationToken = CType(obj, CancellationToken)
For i = 0 To 10
' Simulating work.
System.Threading.Thread.Yield()
Thread.Sleep(1000)
SyncLock (LockObj)
SillyValue += 1
End SyncLock
If token.IsCancellationRequested Then
SyncLock (LockObj)
bAllDone = True
End SyncLock
Exit For
End If
Next
SyncLock (LockObj)
bAllDone = True
End SyncLock
End Sub
Protected Sub Timer1_Tick(sender As Object, e As System.EventArgs) Handles Timer1.Tick
' '***** This is for ending the task normally.
If bAllDone Then
If bInterrupted Then
DoStatusMessage("Processing terminated by user")
Else
DoStatusMessage("This Task has has completed normally.")
End If
'Timer1.Change(System.Threading.Timeout.Infinite, 0)
Timer1.Enabled = False
StopThread()
Exit Sub
End If
DoStatusMessage("Working:" & CStr(SillyValue))
End Sub
End Class
今C#:
using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Web;
using System.Threading.Tasks;
using System.Threading;
public class Directory4 : System.Web.UI.Page
{
private static CancellationTokenSource cts = null;
private static object LockObj = new object();
private static int SillyValue = 0;
private static bool bInterrupted = false;
private static bool bAllDone = false;
protected void Page_Load(object sender, System.EventArgs e)
{
}
protected void DoStatusMessage(string Msg)
{
this.lblMessages.Text = Msg;
Debug.Print(Msg);
}
protected void btn_Start_Click(object sender, EventArgs e)
{
if ((cts != null)) {
if (!cts.IsCancellationRequested) {
DoStatusMessage("Please cancel the running process first.");
return;
}
cts.Dispose();
cts = null;
DoStatusMessage("Plase cancel the running process or wait for it to complete.");
}
bInterrupted = false;
bAllDone = false;
CancellationTokenSource ncts = new CancellationTokenSource();
cts = ncts;
// Pass the token to the cancelable operation.
ThreadPool.QueueUserWorkItem(new WaitCallback(DoSomeWork), cts.Token);
DoStatusMessage("This Task has now started.");
Timer1.Interval = 1000;
Timer1.Enabled = true;
}
protected void StopThread()
{
if ((cts == null))
return;
lock ((LockObj)) {
cts.Cancel();
System.Threading.Thread.SpinWait(1);
cts.Dispose();
cts = null;
bAllDone = true;
}
}
protected void btn_Stop_Click(object sender, EventArgs e)
{
if (bAllDone) {
DoStatusMessage("Nothing running. Start the task if you like.");
return;
}
bInterrupted = true;
btn_Start.Enabled = true;
StopThread();
DoStatusMessage("This Canceled Task has now been gently terminated.");
}
public void Refresh_Parent_Webpage_and_Exit()
{
//***** This refreshes the parent page.
String csname1 = "Exit_from_Dir4";
Type cstype = GetType();
// Get a ClientScriptManager reference from the Page class.
ClientScriptManager cs = Page.ClientScript;
// Check to see if the startup script is already registered.
if (!cs.IsStartupScriptRegistered(cstype, csname1)) {
StringBuilder cstext1 = new StringBuilder();
cstext1.Append("<script language=javascript>window.close();</script>");
cs.RegisterStartupScript(cstype, csname1, cstext1.ToString());
}
}
//Thread 2: The worker
public static void DoSomeWork(CancellationToken token)
{
int i = 0;
if ((token == null)) {
Debug.Print("Empty cancellation token passed.");
return;
}
lock ((LockObj)) {
SillyValue = 0;
}
//Dim token As CancellationToken = CType(obj, CancellationToken)
for (i = 0; i <= 10; i++) {
// Simulating work.
System.Threading.Thread.Yield();
Thread.Sleep(1000);
lock ((LockObj)) {
SillyValue += 1;
}
if (token.IsCancellationRequested) {
lock ((LockObj)) {
bAllDone = true;
}
break; // TODO: might not be correct. Was : Exit For
}
}
lock ((LockObj)) {
bAllDone = true;
}
}
protected void Timer1_Tick(object sender, System.EventArgs e)
{
// '***** This is for ending the task normally.
if (bAllDone) {
if (bInterrupted) {
DoStatusMessage("Processing terminated by user");
} else {
DoStatusMessage("This Task has has completed normally.");
}
//Timer1.Change(System.Threading.Timeout.Infinite, 0)
Timer1.Enabled = false;
StopThread();
return;
}
DoStatusMessage("Working:" + Convert.ToString(SillyValue));
}
public Directory4()
{
Load += Page_Load;
}
}
コードをお楽しみください!