Как сбросить CancellationTokenSource и отладить многопоточность с помощью VS2010?
я использовал 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:tempIn", "*.jpg", SearchOption.AllDirectories);
    string newDir = @"C:tempOut";
    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();
}
5 ответов
Вопрос 1> Как сбросить CancellationTokenSource после первого использования времени?
если вы отмените его, то он отменен и не может быть восстановлен. Вам нужен новый CancellationTokenSource. А CancellationTokenSource это не какая-то фабрика. Это просто владелец одного токена. ИМО это должно было называться CancellationTokenOwner.
Вопрос 2> как отладить многопоточность в VS2010? Если я запустить приложение в режиме отладки, я вижу следующее исключение для заявление
это не имеет ничего общего с отладкой. Вы не можете получить доступ к элементу управления gui из другого потока. Вам нужно использовать Invoke для этого. Я думаю, вы видите проблему только в режиме отладки, потому что некоторые проверки отключены в режиме релиза. Но жучок все еще там.
Parallel.ForEach(files, parOpts, (currentFile) =>
{
  ...  
  this.Text =  ...;// <- this assignment is illegal
  ...
});
в разделе отладка > windows в visual studio вы захотите посмотреть окно потоков, окно callstack и окно paralell tasks.
когда отладчик прерывается для исключения, которое вы получаете, вы можете посмотреть в окне callstack, чтобы увидеть, какой поток делает вызов и откуда этот поток исходит.
-редактировать на основе опубликованного скриншота -
вы можете щелкнуть правой кнопкой мыши в стеке вызовов и выбрать "показать внешний код", чтобы увидеть точно что происходит в стеке, но "внешний код" означает "где-то в рамках", поэтому он может быть или не быть полезным (я обычно нахожу его интересным, хотя :) )
на скриншоте мы также видим, что вызов выполняется из потока пула потоков. Если вы посмотрите на окно темы, вы увидите, что один из них имеет желтую стрелку. Это поток, который мы в настоящее время выполняем, и где создается исключение. Имя этого потока - "рабочий поток" , и это означает его из пула нитей.
как уже было отмечено, вы должны сделать любые обновления пользовательского интерфейса из пользовательского потока. Например, для этого можно использовать вызов элемента управления, см. @CodeInChaos awnser.
-edit2-
Я прочитал ваши комментарии на @CodeInChaos awnser и вот один из способов сделать это более TPL, как способ:
Прежде всего, вам нужно получить экземпляр TaskScheduler это будет запускать задачи в потоке пользовательского интерфейса. вы можете сделать это, объявив TaskScheduler в вас ui-класс с именем, например uiScheduler и в конструкторе значение TaskScheduler.FromCurrentSynchronizationContext();
теперь, когда у вас есть, вы можете сделать новую задачу, которая обновляет пользовательский интерфейс:
 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
обратите внимание, что мы передаем планировщик задач в задачу при ее запуске.
существует также второй способ сделать это, который использует API TaskContinuation. Однако мы не можем использовать параллельно.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
  }
то, что мы делаем здесь, делает новую задачу каждый цикл, который будет выполнять работу и генерировать сообщение, затем мы подключаемся к другой задаче, которая фактически обновит пользовательский интерфейс.
вы можете прочитать больше о ContinueWith и continuations здесь
для отладки я определенно рекомендую использовать окно параллельных стеков в сочетании с окном потоков. С помощью окна параллельные стеки вы можете увидеть callstacks всех потоков на одном комбинированном дисплее. Вы можете легко прыгать между потоками и точками в стеке вызовов. Окно параллельные стеки и потоки находится в разделе отладка > Windows.
также еще одна вещь, которая действительно может помочь в отладке, - это включить выбрасывание исключений CLR как при их выбрасывании, так и пользовательским кодом. Для этого перейдите в Debug > Exceptions и включите оба параметра -

Спасибо за всю вашу помощь с резьбой выше здесь. Это помогло мне в моих исследованиях. Я потратил много времени, пытаясь понять это и было нелегко. Разговор с другом тоже очень помог.
когда вы запускаете и останавливаете поток, вы должны быть уверены, что делаете это потокобезопасным способом. Вы также должны иметь возможность перезапустить поток после его остановки. В этом примере я использовал 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" />  
        <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();
некрасиво это ад, но он работает. Я должен найти лучшее решение.
