如何使用VS2010重置CancellationTokenSource并调试多线程?

| 我已经使用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();
}
    
已邀请:
           问题1>首次使用后如何重置CancellationTokenSource? 如果您取消它,则它被取消并且无法还原。您需要一个新的
CancellationTokenSource
CancellationTokenSource
不是某种工厂。它只是单个令牌的所有者。海事组织,它应该被称为“ 4”。   问题2>如何在VS2010中调试多线程?如果以调试模式运行该应用程序,则可以看到以下异常语句 这与调试无关。您无法从另一个线程访问gui控件。您需要使用
Invoke
。我猜您只能在调试模式下看到问题,因为在发布模式下禁用了某些检查。但是错误仍然存​​在。
Parallel.ForEach(files, parOpts, (currentFile) =>
{
  ...  
  this.Text =  ...;// <- this assignment is illegal
  ...
});
    
        在Visual Studio中的“调试”>“窗口”下,您将要查看“线程”窗口,“调用堆栈”窗口和“并行任务”窗口。 当调试器因您遇到的异常而中断时,您可以查看“调用堆栈”窗口,以查看是哪个线程在进行调用以及该线程来自何处。 -根据已发布的屏幕截图进行编辑- 您可以在调用堆栈中单击鼠标右键,然后选择\“显示外部代码\”以查看堆栈中正在发生的事情,但是\“外部代码\”的含义是\“框架中的某个位置\”,因此它可能会也可能不会很有用(尽管我通常觉得很有趣:)) 从您的屏幕截图中,我们还可以看到该调用是由线程池线程发出的。如果您查看线程窗口,将会看到其中一个带有黄色箭头。那就是我们当前正在执行的线程,并且在该线程上会抛出异常。该线程的名称为\'Worker Thread \',这意味着它来自线程池。 如前所述,您必须从用户线程对ui进行任何更新。例如,您可以为此使用控件上的“ Invoke”,请参阅@CodeInChaos awnser。 -edit2- 我通读了您对@CodeInChaos遮篷的评论,这是一种以更类似于TPL的方式进行操作的方法: 首先,您需要掌握一个将在UI线程上运行任务的ѭ7实例。您可以通过在名为
uiScheduler
的ui类中声明ѭ7并将其设置为
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
  }
我们在这里所做的是在每个循环中创建一个新任务,以完成工作并生成消息,然后我们挂接到了另一个实际上将更新ui的任务。 您可以在此处阅读有关ContinueWith和延续的更多信息     
        对于调试,我绝对建议将“并行堆栈”窗口与“线程”窗口结合使用。使用并行堆栈窗口,您可以在一个组合的显示器上看到所有线程的调用堆栈。您可以轻松地在调用堆栈中的线程和点之间跳转。并行堆栈和线程窗口位于调试> Windows中。 另外,在调试中真正有用的另一件事是在引发CLR异常和用户未处理时都打开CLR异常的引发。为此,请转到调试>例外,然后启用两个选项-     
感谢您在此处提供的所有线程帮助。它确实对我的研究有所帮助。我花了很多时间试图弄清楚这一点,这并不容易。与朋友交谈也有很大帮助。 在启动和停止线程时,必须确保以线程安全的方式进行操作。您还必须能够在停止线程后重新启动该线程。在此示例中,我在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;
    }
}
享受代码!     
        我正在使用一个以丑陋的方式欺骗​​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();
丑陋有地狱,但行得通。我最终必须找到更好的解决方案。     

要回复问题请先登录注册