【问题标题】:How to reset the CancellationTokenSource and debug the multithread with VS2010?VS2010如何重置CancellationTokenSource并调试多线程?
【发布时间】:2011-05-29 15:10:59
【问题描述】:

我已经使用 CancellationTokenSource 来提供一个函数,以便用户可以 取消冗长的动作。但是,在用户申请第一次取消后, 后来的进一步行动不再起作用。我的猜测是 CancellationTokenSource 的状态已设置为 Cancel,我想知道如何重置 它回来了。

  • 问题一:第一次使用后如何重置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();
}

【问题讨论】:

  • 如果你取消它,那么它就被取消并且无法恢复。您需要一个新的 CancellationTokenSource。
  • 我在blogs.msdn.com/b/pfxteam/archive/2009/05/22/9635790.aspx 找到了一篇文章,表明我们无法重置它。解决方案是每次创建一个新的 CancellationTokenSource。这回答了我的第一个问题。但是,我的第二个问题仍然需要帮助。 --- 谢谢
  • 第一个问题已回答/解决,第二个问题重复了 100 次。
  • @Henk,我只是不想在这里两次发布我的源代码。 -thx
  • 不是一个很好的理由,尤其是因为你不需要大部分代码。

标签: c# .net winforms debugging cancellationtokensource


【解决方案1】:

问题一>第一次使用后如何重置CancellationTokenSource?

如果您取消它,那么它就会被取消并且无法恢复。您需要一个新的CancellationTokenSourceCancellationTokenSource 不是某种工厂。它只是一个令牌的所有者。 IMO 它应该被称为CancellationTokenOwner

问题2> 如何调试VS2010中的多线程?如果我在调试模式下运行应用程序,我可以看到语句的以下异常

这与调试无关。您不能从另一个线程访问 gui 控件。为此,您需要使用Invoke。我猜你只在调试模式下看到问题,因为在发布模式下禁用了一些检查。但是bug依然存在。

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

【讨论】:

  • 您的意思是原始代码包含错误吗?如果可能,请指定。代码复制自 Prof C#2010 和 .NET 4.0 第 766 页。 -- thx
  • @q0987 是的,该代码已损坏。这正是异常试图告诉你的。从另一个线程访问 WinForms 控件是一个典型的初学者错误。
  • 你能告诉我如何解决吗?书中介绍了这种错误的方法,我需要知道正确的方法。 --thx
  • 使用Control.Invoke 是传统的答案。
  • 正如pp761一书中提到的“随着.NET 4.0的发布,为您提供了一个全新的并行编程库。使用System.Threading.Tasks的类型,您可以构建细粒度的,可扩展的并行代码,无需直接使用线程或线程池。”我想知道如何使用基于 TPL 的解决方案来解决问题。 --thx
【解决方案2】:

在 Visual Studio 中的 Debug > windows 下,您需要查看线程窗口、调用堆栈窗口和并行任务窗口。

当调试器因您遇到的异常而中断时,您可以查看调用堆栈窗口以查看正在执行调用的线程以及该线程来自何处。

-根据发布的屏幕截图进行编辑-

您可以右键单击调用堆栈并选择“显示外部代码”以查看堆栈中发生的确切情况,但“外部代码”表示“框架中的某处”,因此它可能有用也可能没有用(不过我通常觉得它很有趣:))

从您的屏幕截图中,我们还可以看到调用是从线程池线程发出的。如果您查看线程窗口,您会看到其中一个带有黄色箭头。那就是我们当前正在执行的线程,也是抛出异常的地方。这个线程的名字是'Worker Thread',意思是它来自线程池。

如前所述,您必须从用户线程对您的 ui 进行任何更新。例如,您可以为此使用控件上的“Invoke”,请参阅@CodeInChaos awnser。

-edit2-

我在@CodeInChaos awnser 上阅读了您的 cmets,这是一种更类似于 TPL 的方法: 首先,您需要获取一个TaskScheduler 的实例,它将在 UI 线程上运行任务。您可以通过在命名为 uiScheduler 的 ui 类中声明 TaskScheduler 并在构造函数中将其设置为 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。但是我们不能再使用 Paralell.Foreach,但我们将使用常规的 foreach 和任务。关键是任务允许您安排另一个任务,该任务将在第一个任务完成后运行。但是第二个任务不必在同一个调度程序上运行,这对我们现在非常有用,因为我们想在后台做一些工作,然后更新 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
  }

我们在这里所做的是在每个循环中创建一个新任务来完成工作并生成消息,然后我们将挂钩另一个实际更新用户界面的任务。

您可以阅读更多关于 ContinueWith 和延续 here

【讨论】:

    【解决方案3】:

    对于调试,我绝对建议将 Parallel Stacks 窗口与 Threads 窗口结合使用。使用并行堆栈窗口,您可以在一个组合显示器上查看所有线程的调用堆栈。您可以轻松地在调用堆栈中的线程和点之间跳转。并行堆栈和线程窗口位于 Debug > Windows。

    另外一件真正有助于调试的事情是在 CLR 异常被抛出和用户未处理时都开启抛出异常。为此,请转到 Debug > Exceptions,并启用这两个选项 -

    【讨论】:

      【解决方案4】:

      感谢您在此处对上述线程提供的所有帮助。它确实帮助了我的研究。我花了很多时间试图弄清楚这一点,但这并不容易。与朋友交谈也有很大帮助。

      当您启动和停止线程时,您必须确保以线程安全的方式进行。您还必须能够在停止线程后重新启动它。在此示例中,我在 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;
          }
      }
      

      享受代码!

      【讨论】:

        【解决方案5】:

        我正在使用一个以丑陋的方式欺骗​​ 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();
        

        它很丑有地狱,但它有效。我最终必须找到更好的解决方案。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-07-27
          • 2017-09-01
          • 2010-10-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多