Respuestas:
Una de las formas más sencillas de obtener un valor de retorno de un hilo es utilizar cierres. Cree una variable que contendrá 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 de trabajo y luego, una vez que ese hilo termine, puede usarlo desde el hilo principal.
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
}
value
están realizando lecturas ni escrituras al mismo tiempo. Pero, sí, siempre tenga en cuenta cuándo es necesario un candado.
Depende de cómo desee crear el hilo y la versión de .NET disponible:
.NET 2.0+:
A) Puede crear el Thread
objeto directamente. En este caso, podría usar "cierre" - declarar variable y capturarla usando 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 delegados y un IAsyncResult
valor de retorno del EndInvoke()
método:
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 BackgroundWorker
clase. En este caso, puede usar la variable capturada (como con el Thread
objeto) o manejar el RunWorkerCompleted
evento:
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 Task Parallel Library y Task
class para iniciar sus hilos. La clase genérica le Task<TResult>
permite obtener el valor de retorno de la Result
propiedad:
//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 async
/ await
keywords para devolver el valor de la tarea directamente en lugar de obtener la Result
propiedad:
int result = await Task.Run(() => {
//Some work...
return 42; });
Nota: el método que contiene el código anterior debe estar marcado con la async
palabra clave.
Por muchas razones, el uso de Task Parallel Library es una forma preferible de trabajar con subprocesos.
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. A continuación, se muestra un código de muestra para una aplicación de consola que usa 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
Vea la respuesta de @ Roger a continuación.
https://stackoverflow.com/a/24916747/141172
Señala que puede usar una Tarea que devuelva un Task<T>
, y compruebe Task<T>.Result
.
Un hilo no es un método, normalmente no "devuelve" un valor.
Sin embargo, si está tratando de recuperar un valor de los resultados de algún procesamiento, tiene muchas opciones, las dos principales son:
Realmente depende de cómo esté creando el hilo y cómo quiera usarlo, así como del lenguaje / marco / herramientas que esté usando.
Mi clase favorita, ejecuta cualquier método en otro hilo con solo 2 líneas de código.
class ThreadedExecuter<T> 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<string> executer = new ThreadedExecuter<string>(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.
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 serie excelente sobre subprocesos en Threading en C # .
Simplemente use el enfoque de delegado.
int val;
Thread thread = new Thread(() => { val = Multiply(1, 2); });
thread.Start();
Ahora haga la función Multiplicar que funcionará en otro hilo:
int Multiply(int x, int y)
{
return x * y;
}
Me encontré con este hilo cuando también intentaba obtener el valor de retorno de un método que se ejecuta dentro de un hilo. Pensé en publicar mi solución que funciona.
Esta solución utiliza una clase para almacenar tanto el método a ejecutar (indirectamente) como el valor devuelto. La clase se puede utilizar para cualquier función y cualquier tipo de retorno. 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<T>
{
private T mResult;
public T Result
{
get { return mResult; }
private set { mResult = value; }
}
public ThreadedMethod()
{
}
//If supporting .net 3.5
public void ExecuteMethod(Func<T> 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<bool> threadedMethod = new ThreadedMethod<bool>();
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 ThreadedMethod
clase, así que haremos que ExecuteMethod
devuelva el valor de la función. Esto no duele 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 último .NET Framework, es posible devolver un valor de un subproceso separado usando una Tarea, donde la propiedad Result bloquea el subproceso que llama hasta que finaliza la tarea:
Task<MyClass> task = Task<MyClass>.Factory.StartNew(() =>
{
string s = "my message";
double d = 3.14159;
return new MyClass { Name = s, Number = d };
});
MyClass test = task.Result;
Para obtener más información, consulte http://msdn.microsoft.com/en-us/library/dd537613(v=vs.110).aspx
Los delegados de ThreadStart en C # que se usan para iniciar los subprocesos 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 adecuada y segura para el hilo) y leer desde allí cuando el hilo haya terminado de ejecutarse.
Si no desea usar un BackgroundWorker y solo 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));
}
/// <summary>
/// Occurs when processing has finished or an error occurred.
/// </summary>
public event AsyncCompletedEventHandler Thread1Completed;
protected virtual void OnThread1Completed(AsyncCompletedEventArgs e)
{
//copy locally
AsyncCompletedEventHandler handler = Thread1Completed;
if (handler != null)
{
handler(this, e);
}
}
#endregion
}
}
thread1_
parte de su cableado del AsyncCompletedEventHandler . Si mi edición fue un error, por favor ayúdenme a comprender lo que estaba sucediendo allí.
thread1_Thread1Completed +=
porque thread1_Thread1Completed
es el nombre de una función, por lo que no puede ponerlo en el lado izquierdo de un operador de asignación. El lado izquierdo Thread1Completed +=
se usa porque es un evento, por lo que puede aparecer en el lado izquierdo del operador de asignación para agregar controladores de eventos. Verpublic event AsyncCompletedEventHandler Thread1Completed;
#region
sección antes. Miré. ¡Honesto! :)
Los hilos realmente no tienen valores de retorno. Sin embargo, si crea un delegado, puede invocarlo de forma asincrónica a través del BeginInvoke
método. Esto ejecutará el método en un subproceso de grupo de subprocesos. Puede obtener cualquier valor de retorno, como llamar a través de EndInvoke
.
Ejemplo:
static int GetAnswer() {
return 42;
}
...
Func<int> 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 agradable cuando se desarrollan para Windows Forms.
Supongamos que desea aprobar una clase simple de un lado a otro:
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:
Desde dentro de la rutina del hilo:
Agregar un delegado puede ser útil para publicar sus datos directamente en su hilo principal, pero es posible que deba usar Invoke si algunos de los elementos de datos no son seguros para el hilo.
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<Thread>(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<Anything> m_list;
static void Main(string[] args) {
m_list = new List<Anything>(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:
Espero que otros puedan entender lo que he intentado explicar.
Disfruto trabajando en hilos y usando delegados. Hacen que C # sea muy divertido.
Quería ver qué implicaba escribir el código anterior como una aplicación de consola VB. La conversión involucró algunas cosas que no esperaba, así que actualizaré este hilo aquí para aquellos que quieran saber cómo hilo 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";
}
}
test(){ Thread.Sleep(5000); /*Highly time demanding process*/ return "Returned from test()";}
. En este caso, el hilo independiente no tendría tiempo para asignar un nuevo valor a la returnValue
variable. Como último recurso, puede guardar una referencia de hilo var standaloneThread = new Thread(()=> //...);
y después de eso, iniciarlo de forma sincronizada standaloneThread.Start(); standaloneThread.Join();
. Pero esta ciertamente no es la mejor práctica.
Una solución simple es pasar un parámetro por referencia a la función que se está ejecutando en el hilo y cambiar su valor en el hilo.
// create a list of threads
List<Thread> threads = new List<Thread>();
//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 en otra función:
bool theirFunction(var someVar){
return false;
}
void myFunction(var someVar ref bool result){
result = theirFunction(myVar);
}
Puede usar este código:
private Object MyThread(Object Data)
{
Object response = null;
Thread newThread = new Thread(() =>
{
response = MyFunction(Data);
//MyFunction Is Function that you Define
});
newThread.Start();
newThread.Join();
return response;
}
lock(value) { value = "Hello world"; }
sería mejor manejar la escritura de valores de múltiples hilos?