¿Devolviendo un valor del hilo?

¿Cómo devuelvo un valor de un hilo?

Una de las maneras más fáciles de obtener un valor de retorno de un hilo es usar cierres. Cree una variable que contenga el valor de retorno del hilo y luego capturelo en una expresión lambda. Asigne el valor de “retorno” a esta variable desde el hilo del trabajador y una vez que termine dicho hilo, puede usarlo desde el hilo padre.

void Main() { object value = null; // Used to store the return value var thread = new Thread( () => { value = "Hello World"; // Publish the return value }); thread.Start(); thread.Join(); Console.WriteLine(value); // Use the return value here } 

Usaría el enfoque BackgroundWorker y devolvería el resultado en e.Result.

EDITAR:

Esto se asocia comúnmente con WinForms y WPF, pero puede ser utilizado por cualquier tipo de aplicación .NET. Aquí hay un código de muestra para una aplicación de consola que utiliza BackgroundWorker:

 using System; using System.Threading; using System.ComponentModel; using System.Collections.Generic; using System.Text; namespace BGWorker { class Program { static bool done = false; static void Main(string[] args) { BackgroundWorker bg = new BackgroundWorker(); bg.DoWork += new DoWorkEventHandler(bg_DoWork); bg.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bg_RunWorkerCompleted); bg.RunWorkerAsync(); while (!done) { Console.WriteLine("Waiting in Main, tid " + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(100); } } static void bg_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { Console.WriteLine("Completed, tid " + Thread.CurrentThread.ManagedThreadId); done = true; } static void bg_DoWork(object sender, DoWorkEventArgs e) { for (int i = 1; i <= 5; i++) { Console.WriteLine("Work Line: " + i + ", tid " + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(500); } } } } 

Salida:

 Waiting in Main, tid 10 Work Line: 1, tid 6 Waiting in Main, tid 10 Waiting in Main, tid 10 Waiting in Main, tid 10 Waiting in Main, tid 10 Waiting in Main, tid 10 Work Line: 2, tid 6 Waiting in Main, tid 10 Waiting in Main, tid 10 Waiting in Main, tid 10 Waiting in Main, tid 10 Waiting in Main, tid 10 Work Line: 3, tid 6 Waiting in Main, tid 10 Waiting in Main, tid 10 Waiting in Main, tid 10 Waiting in Main, tid 10 Waiting in Main, tid 10 Work Line: 4, tid 6 Waiting in Main, tid 10 Waiting in Main, tid 10 Waiting in Main, tid 10 Waiting in Main, tid 10 Work Line: 5, tid 6 Waiting in Main, tid 10 Waiting in Main, tid 10 Waiting in Main, tid 10 Waiting in Main, tid 10 Waiting in Main, tid 10 Waiting in Main, tid 10 Completed, tid 6 

ACTUALIZACIÓN 2014

Ver la respuesta de @ Roger a continuación.

https://stackoverflow.com/a/24916747/141172

Él señala que puede usar una Tarea que devuelve una Task , y marcar Task.Result .

Un hilo no es un método: normalmente no “devuelve” un valor.

Sin embargo, si intenta recuperar un valor de los resultados de algún procesamiento, tiene muchas opciones, las dos principales son:

  • Puede sincronizar una pieza de datos compartida y configurarla de manera adecuada.
  • También puede pasar los datos nuevamente en alguna forma de callback.

Realmente depende de cómo está creando el hilo y cómo quiere usarlo, así como del lenguaje / marco / herramientas que está utilizando.

Depende de cómo quiera crear el hilo y la versión .NET disponible:

.NET 2.0+:

A) Puede crear el objeto Thread directamente. En este caso, podría usar “cierre” – declarar la variable y capturarla usando la expresión lambda:

 object result = null; Thread thread = new System.Threading.Thread(() => { //Some work... result = 42; }); thread.Start(); thread.Join(); Console.WriteLine(result); 

B) Puede usar delegates y IAsyncResult y devolver valor desde el método EndInvoke() :

 delegate object MyFunc(); ... MyFunc x = new MyFunc(() => { //Some work... return 42; }); IAsyncResult asyncResult = x.BeginInvoke(null, null); object result = x.EndInvoke(asyncResult); 

C) Puedes usar la clase BackgroundWorker . En este caso, podría usar la variable capturada (como con el objeto Thread ) o manejar el evento RunWorkerCompleted :

 BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += (s, e) => { //Some work... e.Result = 42; }; worker.RunWorkerCompleted += (s, e) => { //e.Result "returned" from thread Console.WriteLine(e.Result); }; worker.RunWorkerAsync(); 

.NET 4.0+:

A partir de .NET 4.0, puede usar la Biblioteca de tareas paralelas y Task clase Task para iniciar sus hilos. La clase genérica Task permite obtener el valor de retorno de la propiedad Result :

 //Main thread will be blocked until task thread finishes //(because of obtaining the value of the Result property) int result = Task.Factory.StartNew(() => { //Some work... return 42;}).Result; 

.NET 4.5+:

A partir de .NET 4.5 también puede usar palabras clave async / await para devolver el valor de la tarea directamente en lugar de obtener la propiedad Result :

 int result = await Task.Run(() => { //Some work... return 42; }); 

Nota: el método, que contiene el código anterior, debe estar marcado con la palabra clave async .

Por muchas razones, el uso de Task Parallel Library es la forma preferible de trabajar con hilos.

Aquí hay un ejemplo simple usando un delegado …

 void Main() { DoIt d1 = Doer.DoThatThang; DoIt d2 = Doer.DoThatThang; IAsyncResult r1 = d1.BeginInvoke( 5, null, null ); IAsyncResult r2 = d2.BeginInvoke( 10, null, null ); Thread.Sleep( 1000 ); var s1 = d1.EndInvoke( r1 ); var s2 = d2.EndInvoke( r2 ); s1.Dump(); // You told me 5 s2.Dump(); // You told me 10 } public delegate string DoIt( int x ); public class Doer { public static string DoThatThang( int x ) { return "You told me " + x.ToString(); } } 

Hay una excelente serie sobre cómo enhebrar en Threading en C # .

Mi clase favorita, ejecuta cualquier método en otro hilo con solo 2 líneas de código.

 class ThreadedExecuter where T : class { public delegate void CallBackDelegate(T returnValue); public delegate T MethodDelegate(); private CallBackDelegate callback; private MethodDelegate method; private Thread t; public ThreadedExecuter(MethodDelegate method, CallBackDelegate callback) { this.method = method; this.callback = callback; t = new Thread(this.Process); } public void Start() { t.Start(); } public void Abort() { t.Abort(); callback(null); //can be left out depending on your needs } private void Process() { T stuffReturned = method(); callback(stuffReturned); } } 

uso

  void startthework() { ThreadedExecuter executer = new ThreadedExecuter(someLongFunction, longFunctionComplete); executer.Start(); } string someLongFunction() { while(!workComplete) WorkWork(); return resultOfWork; } void longFunctionComplete(string s) { PrintWorkComplete(s); } 

Tenga en cuenta que longFunctionComplete NO se ejecutará en el mismo hilo que starthework.

Para los métodos que toman parámetros, siempre puede usar cierres o expandir la clase.

Encontré este hilo cuando también trato de obtener el valor de retorno de un método que se ejecuta dentro de un hilo. Pensé que publicaría mi solución que funciona.

Esta solución utiliza una clase para almacenar el método que se ejecutará (indirectamente) y almacena el valor de retorno. La clase se puede usar para cualquier función y cualquier tipo de devolución. Simplemente crea una instancia del objeto usando el tipo de valor de retorno y luego pasa la función para llamar a través de un lambda (o delegado).


Implementación de C # 3.0


 public class ThreadedMethod { private T mResult; public T Result { get { return mResult; } private set { mResult = value; } } public ThreadedMethod() { } //If supporting .net 3.5 public void ExecuteMethod(Func func) { Result = func.Invoke(); } //If supporting only 2.0 use this and //comment out the other overload public void ExecuteMethod(Delegate d) { Result = (T)d.DynamicInvoke(); } } 

Para usar este código, puede usar un Lambda (o un delegado). Aquí está el ejemplo usando lambdas:

 ThreadedMethod threadedMethod = new ThreadedMethod(); Thread workerThread = new Thread((unused) => threadedMethod.ExecuteMethod(() => SomeMethod())); workerThread.Start(); workerThread.Join(); if (threadedMethod.Result == false) { //do something about it... } 

Implementación de VB.NET 2008


Cualquiera que use VB.NET 2008 no puede usar lambdas con métodos de devolución sin valor. Esto afecta a la clase ThreadedMethod , por lo que haremos que ExecuteMethod devuelva el valor de la función. Esto no lastima nada.

 Public Class ThreadedMethod(Of T) Private mResult As T Public Property Result() As T Get Return mResult End Get Private Set(ByVal value As T) mResult = value End Set End Property Sub New() End Sub 'If supporting .net 3.5' Function ExecuteMethod(ByVal func As Func(Of T)) As T Result = func.Invoke() Return Result End Function 'If supporting only 2.0 use this and' 'comment out the other overload' Function ExecuteMethod(ByVal d As [Delegate]) As T Result = DirectCast(d.DynamicInvoke(), T) Return Result End Function End Class 

Con el .NET Framework más reciente, es posible devolver un valor de un subproceso separado utilizando una Tarea, donde la propiedad Result bloquea el subproceso que realiza la llamada hasta que finaliza la tarea:

  Task task = Task.Factory.StartNew(() => { string s = "my message"; double d = 3.14159; return new MyClass { Name = s, Number = d }; }); MyClass test = task.Result; 

Para obtener detalles, consulte http://msdn.microsoft.com/en-us/library/dd537613(v=vs.110).aspx

Si no quiere utilizar un BackgroundWorker, y simplemente usa un Thread normal, puede activar un evento para devolver datos como este:

 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Threading; namespace ThreadWithDataReturnExample { public partial class Form1 : Form { private Thread thread1 = null; public Form1() { InitializeComponent(); thread1 = new Thread(new ThreadStart(this.threadEntryPoint)); Thread1Completed += new AsyncCompletedEventHandler(thread1_Thread1Completed); } private void startButton_Click(object sender, EventArgs e) { thread1.Start(); //Alternatively, you could pass some object //in such as Start(someObject); //With apprioriate locking, or protocol where //no other threads access the object until //an event signals when the thread is complete, //any other class with a reference to the object //would be able to access that data. //But instead, I'm going to use AsyncCompletedEventArgs //in an event that signals completion } void thread1_Thread1Completed(object sender, AsyncCompletedEventArgs e) { if (this.InvokeRequired) {//marshal the call if we are not on the GUI thread BeginInvoke(new AsyncCompletedEventHandler(thread1_Thread1Completed), new object[] { sender, e }); } else { //display error if error occurred //if no error occurred, process data if (e.Error == null) {//then success MessageBox.Show("Worker thread completed successfully"); DataYouWantToReturn someData = e.UserState as DataYouWantToReturn; MessageBox.Show("Your data my lord: " + someData.someProperty); } else//error { MessageBox.Show("The following error occurred:" + Environment.NewLine + e.Error.ToString()); } } } #region I would actually move all of this into it's own class private void threadEntryPoint() { //do a bunch of stuff //when you are done: //initialize object with data that you want to return DataYouWantToReturn dataYouWantToReturn = new DataYouWantToReturn(); dataYouWantToReturn.someProperty = "more data"; //signal completion by firing an event OnThread1Completed(new AsyncCompletedEventArgs(null, false, dataYouWantToReturn)); } ///  /// Occurs when processing has finished or an error occurred. ///  public event AsyncCompletedEventHandler Thread1Completed; protected virtual void OnThread1Completed(AsyncCompletedEventArgs e) { //copy locally AsyncCompletedEventHandler handler = Thread1Completed; if (handler != null) { handler(this, e); } } #endregion } } 

Los delegates de ThreadStart en C # utilizados para iniciar los hilos tienen el tipo de retorno ‘void’.

Si desea obtener un ‘valor de retorno’ de un hilo, debe escribir en una ubicación compartida (de una manera segura a prueba de hilos apropiada) y leer de eso cuando el hilo haya terminado de ejecutarse.

Simplemente use el enfoque delegado.

 int val; Thread thread = new Thread(() => { val = Multiply(1, 2); }); thread.Start(); 

Ahora haz la función Multiplicar que funcionará en otro hilo:

 int Multiply(int x, int y) { return x * y; } 

Los hilos realmente no tienen valores devueltos. Sin embargo, si crea un delegado, puede invocarlo de forma asincrónica a través del método BeginInvoke . Esto ejecutará el método en un hilo del grupo de subprocesos. Puede obtener cualquier valor devuelto, como, por ejemplo, una llamada a través de EndInvoke .

Ejemplo:

 static int GetAnswer() { return 42; } ... Func method = GetAnswer; var res = method.BeginInvoke(null, null); // provide args as needed var answer = method.EndInvoke(res); 

GetAnswer se ejecutará en un subproceso de grupo de subprocesos y, cuando se complete, puede recuperar la respuesta a través de EndInvoke como se muestra.

El BackgroundWorker es bueno cuando se desarrolla para Windows Forms.

Digamos que quería pasar una clase simple de ida y vuelta:

 class Anything { // Number and Text are for instructional purposes only public int Number { get; set; } public string Text { get; set; } // Data can be any object - even another class public object Data { get; set; } } 

Escribí una clase corta que hace lo siguiente:

  • Crear o borrar una lista
  • Comience un ciclo
  • En un bucle, crea un nuevo elemento para la lista
  • En bucle, crea un hilo
  • En bucle, envíe el artículo como un parámetro al hilo
  • En bucle, comienza el hilo
  • En bucle, agregue hilo a la lista para mirar
  • Después del ciclo, únete a cada hilo
  • Después de que todas las uniones se hayan completado, muestre los resultados

Desde el interior de la rutina de subprocesos:

  • Bloqueo de llamadas para que solo 1 subproceso pueda ingresar a esta rutina a la vez (otros tienen que esperar)
  • Publicar información sobre el artículo.
  • Modificar el artículo.
  • Cuando el hilo se completa, los datos se muestran en la consola.

Agregar un delegado puede ser útil para publicar sus datos directamente en el hilo principal, pero es posible que deba usar Invocar si algunos de los elementos de datos no son seguros para subprocesos.

 class AnyTask { private object m_lock; public AnyTask() { m_lock = new object(); } // Something to use the delegate public event MainDelegate OnUpdate; public void Test_Function(int count) { var list = new List(count); for (var i = 0; i < count; i++) { var thread = new Thread(new ParameterizedThreadStart(Thread_Task)); var item = new Anything() { Number = i, Text = String.Format("Test_Function #{0}", i) }; thread.Start(item); list.Add(thread); } foreach (var thread in list) { thread.Join(); } } private void MainUpdate(Anything item, bool original) { if (OnUpdate != null) { OnUpdate(item, original); } } private void Thread_Task(object parameter) { lock (m_lock) { var item = (Anything)parameter; MainUpdate(item, true); item.Text = String.Format("{0}; Thread_Task #{1}", item.Text, item.Number); item.Number = 0; MainUpdate(item, false); } } } 

Para probar esto, cree una pequeña aplicación de consola y colóquela en el archivo Program.cs :

 // A delegate makes life simpler delegate void MainDelegate(Anything sender, bool original); class Program { private const int COUNT = 15; private static List m_list; static void Main(string[] args) { m_list = new List(COUNT); var obj = new AnyTask(); obj.OnUpdate += new MainDelegate(ThreadMessages); obj.Test_Function(COUNT); Console.WriteLine(); foreach (var item in m_list) { Console.WriteLine("[Complete]:" + item.Text); } Console.WriteLine("Press any key to exit."); Console.ReadKey(); } private static void ThreadMessages(Anything item, bool original) { if (original) { Console.WriteLine("[main method]:" + item.Text); } else { m_list.Add(item); } } } 

Aquí hay una captura de pantalla de lo que obtuve con esto:

Salida de consola

Espero que otros puedan entender lo que he intentado explicar.

Me gusta trabajar en hilos y usar delegates. Hacen que C # sea muy divertido.

Apéndice: para codificadores de VB

Quería ver qué implicaba escribir el código anterior como una aplicación de consola VB. La conversión implicó algunas cosas que no esperaba, así que actualizaré este hilo aquí para aquellos que quieran saber cómo enhebrar en VB.

 Imports System.Threading Delegate Sub MainDelegate(sender As Anything, original As Boolean) Class Main Private Const COUNT As Integer = 15 Private Shared m_list As List(Of Anything) Public Shared Sub Main(args As String()) m_list = New List(Of Anything)(COUNT) Dim obj As New AnyTask() AddHandler obj.OnUpdate, New MainDelegate(AddressOf ThreadMessages) obj.Test_Function(COUNT) Console.WriteLine() For Each item As Anything In m_list Console.WriteLine("[Complete]:" + item.Text) Next Console.WriteLine("Press any key to exit.") Console.ReadKey() End Sub Private Shared Sub ThreadMessages(item As Anything, original As Boolean) If original Then Console.WriteLine("[main method]:" + item.Text) Else m_list.Add(item) End If End Sub End Class Class AnyTask Private m_lock As Object Public Sub New() m_lock = New Object() End Sub ' Something to use the delegate Public Event OnUpdate As MainDelegate Public Sub Test_Function(count As Integer) Dim list As New List(Of Thread)(count) For i As Int32 = 0 To count - 1 Dim thread As New Thread(New ParameterizedThreadStart(AddressOf Thread_Task)) Dim item As New Anything() item.Number = i item.Text = String.Format("Test_Function #{0}", i) thread.Start(item) list.Add(thread) Next For Each thread As Thread In list thread.Join() Next End Sub Private Sub MainUpdate(item As Anything, original As Boolean) RaiseEvent OnUpdate(item, original) End Sub Private Sub Thread_Task(parameter As Object) SyncLock m_lock Dim item As Anything = DirectCast(parameter, Anything) MainUpdate(item, True) item.Text = [String].Format("{0}; Thread_Task #{1}", item.Text, item.Number) item.Number = 0 MainUpdate(item, False) End SyncLock End Sub End Class Class Anything ' Number and Text are for instructional purposes only Public Property Number() As Integer Get Return m_Number End Get Set(value As Integer) m_Number = value End Set End Property Private m_Number As Integer Public Property Text() As String Get Return m_Text End Get Set(value As String) m_Text = value End Set End Property Private m_Text As String ' Data can be anything or another class Public Property Data() As Object Get Return m_Data End Get Set(value As Object) m_Data = value End Set End Property Private m_Data As Object End Class 
 class Program { static void Main(string[] args) { string returnValue = null; new Thread( () => { returnValue =test() ; }).Start(); Console.WriteLine(returnValue); Console.ReadKey(); } public static string test() { return "Returning From Thread called method"; } } 

Una solución simple es pasar un parámetro por ref a la función que se ejecuta en el subproceso y cambiar su valor en el subproceso.

  // create a list of threads List threads = new List(); //declare the ref params bool is1 = false; bool is2 = false; threads.Add(new Thread(() => myFunction(someVar, ref is1))); threads.Add(new Thread(() => myFunction(someVar, ref is2))); threads.ForEach(x => x.Start()); // wait for threads to finish threads.ForEach(x => x.Join()); //check the ref params if (!is1) { //do something } if (!is2) { //do somethign else } 

Si no puede cambiar la función que se está ejecutando en la banda de rodadura, puede envolverla con otra función:

  bool theirFunction(var someVar){ return false; } void myFunction(var someVar ref bool result){ result = theirFunction(myVar); } 

No soy un experto en el tema, es por eso que lo hice así:

Creé un archivo de Configuraciones y

Dentro del nuevo hilo:

 Setting.Default.ValueToBeSaved; Setting.Default.Save(); 

Luego recojo ese valor cada vez que lo necesito.

Patrik