2011-05-29 24 views
19

我已經使用CancellationTokenSource提供了一個函數,以便用戶可以取消這個冗長的動作 。但是,在用戶應用第一次取消後,後面的進一步操作不再起作用。我的猜測是CancellationTokenSource的狀態已設置爲取消,我想知道如何重置 。如何重置CancellationTokenSource並使用VS2010調試多線程?

  • 問題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(); 
} 
+6

如果您取消它,則會取消並且無法恢復。你需要一個新的CancellationTokenSource。 – CodesInChaos 2011-05-29 15:12:21

+1

我在這裏發現了一篇文章http://blogs.msdn.com/b/pfxteam/archive/2009/05/22/9635790.aspx,指出我們無法重置它。解決方案是每次創建一個新的CancellationTokenSource。這回答了我的第一個問題。但是,對於第二個問題,我仍然需要幫助。 --- thx – q0987 2011-05-29 15:16:23

+0

對每個問題嘗試1個問題 – 2011-05-29 15:41:26

回答

26

問題1>如何到CancellationTokenSource第一次使用後復位?

如果您取消它,那麼它將被取消並且無法恢復。你需要一個新的CancellationTokenSource。 A CancellationTokenSource不是某種工廠。它只是一個令牌的所有者。 IMO應該叫做CancellationTokenOwner

問題2>如何調試VS2010中的多線程?如果我在調試模式下運行應用程序,我可以看到以下異常

這與調試無關。您不能從另一個線程訪問gui控件。你需要使用Invoke。我猜你只能在調試模式下看到問題,因爲在發佈模式下禁用了一些檢查。但該錯誤仍然存​​在。

Parallel.ForEach(files, parOpts, (currentFile) => 
{ 
    ... 
    this.Text = ...;// <- this assignment is illegal 
    ... 
}); 
+0

你的意思是原始代碼包含錯誤?請說明是否可能。該代碼從C#2010教授和.NET 4.0頁766中複製而來。 - thx – q0987 2011-05-29 16:33:46

+0

@ q0987是的,代碼已損壞。這正是例外試圖告訴你的。從另一個線程訪問WinForms控件是一個典型的初學者錯誤。 – CodesInChaos 2011-05-29 16:39:42

+0

你能告訴我如何解決?本書介紹了這種錯誤的方法,我需要知道正確的方法。 --thx – q0987 2011-05-29 16:44:17

2

在調試>在Visual Studio中的窗戶那裏,你會想看看線程窗口,調用堆棧窗口及並行任務窗口。

當調試程序中斷您所遇到的異常時,您可以查看callstack窗口以查看哪個線程正在進行調用以及線程來自哪裏。

-edit基於發佈screenshot-

你可以右鍵點擊調用堆棧,並選擇「顯示外部代碼」,看看究竟是什麼在堆棧中事,但「外部代碼」是指「在某個地方框架',所以它可能會或可能不會有用(我通常覺得它有趣,雖然:))

從您的屏幕截圖,我們還可以看到,調用beeing從一個線程池線程。如果你看看線程窗口,你會看到其中一個有一個黃色的箭頭。那就是我們當前正在執行的線程以及拋出異常的位置。這個線程的名稱是'工作線程',這意味着它來自線程池。

如前所述,您必須從用戶線程更新您的用戶界面。例如,你可以在控件上使用'Invoke',參見@CodeInChaos awnser。

-edit2-

我通過在@CodeInChaos awnser您的意見閱讀,這裏是一個辦法做到這一點更TPL喜歡的方式:所有的 首先,你需要得到一個TaskScheduler的實例保持它將在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 
:您可以通過在你宣佈一個 TaskScheduler命名例如 uiScheduler並在構造函數將其設置爲 TaskScheduler.FromCurrentSynchronizationContext();

現在,你擁有了它的UI類,你可以做一個新的任務,更新UI做到這一點

請注意,我們在啓動任務調度程序時將任務調度程序傳遞給任務。

還有第二種方法可以做到這一點,它使用TaskContinuation API。但是,我們不能再使用Paralell.Foreach,但我們會使用常規的foreach和任務。關鍵在於任務允許您安排在第一個任務完成後運行的另一個任務。但是,第二個任務不必在同一調度運行,現在,因爲我們想要做在後臺的一些工作,然後更新用戶界面,對我們來說是非常有用的:

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和延續here

+0

請查看屏幕截圖http://i53.tinypic.com/24o81z8.png -thx – q0987 2011-05-29 16:43:23

1

爲了調試我絕對推薦與線程窗口結合使用並行堆棧窗口。使用並行堆棧窗口,您可以在一個組合顯示屏上看到所有線程的調用堆棧。您可以輕鬆地在調用堆棧中的線程和點之間跳轉。在Debug> Windows中找到並行堆棧和線程窗口。

另一個可以真正幫助調試的事情是在拋出和用戶未處理時拋出CLR異常。要做到這一點去調試>例外,並啓用這兩個選項 -

Exceptions Window

0

感謝您與線程上述這裏所有的幫助。它在我的研究中幫助了我。我花了很多時間試圖找出這個問題,這並不容易。與朋友交談也有很大幫助。

當您啓動和停止一個線程時,您必須確保以線程安全的方式進行。停止之後,您還必須能夠重新啓動線程。在這個例子中,我在Web應用程序中使用了VS 2010。無論如何,這裏是第一個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" />&nbsp;&nbsp; 
     <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; 
    } 
} 

享受代碼!

0

我使用的一類,我欺騙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(); 

它是醜陋的地獄了,但它的作品。我最終必須找到更好的解決方案。