source net iscancellationrequested cancellationtoken c# .net winforms debugging cancellationtokensource

c# - net - ¿Cómo restablecer el origen CancelToken y depurar el multiproceso con VS2010?



cancellationtoken source c# (5)

Pregunta 1> ¿Cómo restablecer el CancelTokenSource después del primer uso?

Si lo cancelas, entonces se cancela y no se puede restaurar. Necesitas un nuevo CancellationTokenSource . Una CancellationTokenSource no es algún tipo de fábrica. Es solo el dueño de un token único. En mi opinión, debería haber sido llamado CancellationTokenOwner .

Pregunta 2> ¿Cómo depurar el multiproceso en VS2010? Si ejecuto la aplicación en modo de depuración, puedo ver la siguiente excepción para la declaración

Eso no tiene nada que ver con la depuración. No puedes acceder a un control de GUI desde otro hilo. Necesitas usar Invoke para eso. Supongo que usted ve el problema solo en el modo de depuración porque algunas comprobaciones están desactivadas en el modo de liberación. Pero el error sigue ahí.

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

He utilizado CancelTokenSource para proporcionar una función para que el usuario pueda cancelar la acción prolongada. Sin embargo, después de que el usuario aplica la primera cancelación, la acción posterior posterior ya no funciona. Supongo que el estado de CancelaciónTokenSource se ha establecido en Cancelar y quiero saber cómo restablecerlo.

  • Pregunta 1: ¿Cómo restablecer el CancelTokenSource después del primer uso?

  • Pregunta 2: ¿Cómo depurar el multiproceso en VS2010? Si ejecuto la aplicación en modo de depuración, puedo ver la siguiente excepción para la declaración

    this.Text = string.Format("Processing {0} on thread {1}", filename, Thread.CurrentThread.ManagedThreadId);

La InvalidOperaationException no fue manejada por el código de usuario. La operación entre hilos no es válida: se puede acceder a ''MainForm'' del control desde un hilo diferente al hilo en el que se creó.

Gracias.

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(); }


Debajo de Debug> windows en visual studio, querrás mirar la ventana de hilos, la ventana de la pila de llamadas y la ventana de tareas paralelas.

Cuando el depurador se rompe por la excepción que está recibiendo, puede mirar la ventana de la pila de llamadas para ver qué hilo está haciendo la llamada y de dónde viene ese hilo.

-edit basado en screenshot publicado

puede hacer clic con el botón derecho en la pila de llamadas y seleccionar ''mostrar código externo'' para ver exactamente lo que está sucediendo en la pila, pero ''código externo'' significa ''en algún lugar del marco'', por lo que puede o no ser útil (generalmente encuentro aunque interesante :))

Desde su captura de pantalla también podemos ver que la llamada se está realizando desde un subproceso de grupo de subprocesos. Si miras la ventana de hilos, verás que uno de ellos tiene una flecha amarilla. Ese es el hilo que estamos ejecutando actualmente y donde se está lanzando la excepción. El nombre de este subproceso es "Subproceso de trabajo" y eso significa que proviene del grupo de subprocesos.

Como ya se ha señalado, debe realizar actualizaciones en su interfaz de usuario desde el hilo de usuario. Por ejemplo, puede usar el ''Invoke'' en el control para esto, vea @CodeInChaos awnser.

-editar2-

Leí sus comentarios en @CodeInChaos awnser y aquí hay una forma de hacerlo de una manera más TPL: en primer lugar, debe obtener una instancia de TaskScheduler que ejecutará tareas en el subproceso de la interfaz de usuario. puede hacer esto declarando un TaskScheduler en su clase ui llamada por ejemplo uiScheduler y en el constructor configurándolo en TaskScheduler.FromCurrentSynchronizationContext();

Ahora que lo tiene, puede hacer una nueva tarea que actualice la interfaz de usuario:

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

Tenga en cuenta que pasamos el programador de tareas a la tarea cuando lo iniciamos.

También hay una segunda forma de hacerlo, que utiliza las API de TaskContinuation. Sin embargo, ya no podemos usar Paralell.Foreach, pero usaremos una tarea y tareas regulares. la clave es que una tarea le permite programar otra tarea que se ejecutará una vez que se realiza la primera tarea. Pero la segunda tarea no tiene que ejecutarse en el mismo programador y eso es muy útil para nosotros en este momento, ya que queremos trabajar en segundo plano y luego actualizar la interfaz de usuario:

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 }

Lo que estamos haciendo aquí es hacer una nueva tarea en cada bucle que hará el trabajo y generará el mensaje, luego nos engancharemos en otra tarea que realmente actualizará la interfaz de usuario.

Puedes leer más sobre ContinueWith y continuaciones here


Estoy usando una clase en la que engaño a un CancelTokenSource de una manera fea:

//.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();

Es feo tiene el infierno, pero funciona. Eventualmente debo encontrar una mejor solución.


Gracias por toda su ayuda con los hilos de arriba. Me ayudó en mi investigación. Pasé mucho tiempo tratando de resolver esto y no fue fácil. Hablar con un amigo también ayudó mucho.

Cuando inicie y detenga un subproceso, debe asegurarse de hacerlo de forma segura. También debe poder reiniciar el hilo después de detenerlo. En este ejemplo utilicé VS 2010 en una aplicación web. De todos modos aquí está el html primero. Debajo está el código detrás primero en vb.net y luego en C #. Tenga en cuenta que la versión C # es una traducción.

Primero el 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>

El siguiente es el 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

Ahora el 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; } }

Disfruta el código!


Para la depuración definitivamente recomiendo usar la ventana de pilas paralelas junto con la ventana de subprocesos. Usando la ventana de pilas paralelas puede ver las pilas de llamadas de todos los hilos en una pantalla combinada. Puede saltar fácilmente entre hilos y puntos en la pila de llamadas. Las ventanas de pilas y subprocesos paralelos se encuentran en Depurar> Windows.

Otra cosa que realmente puede ayudar en la depuración es activar el lanzamiento de excepciones CLR cuando se lanzan Y el usuario no las maneja. Para hacer esto, vaya a Depurar> Excepciones, y habilite ambas opciones -