Comment puis-je retourner une valeur à partir d'un thread?
L’un des moyens les plus simples d’obtenir une valeur de retour d’un thread est d’utiliser des fermetures. Créez une variable qui contiendra la valeur de retour du thread, puis capturez-la dans une expression lambda. Attribuez la valeur "return" à cette variable à partir du thread de travail, puis une fois ce thread terminé, vous pouvez l'utiliser à partir du thread parent.
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
}
J'utiliserais l'approche BackgroundWorker et renverrais le résultat dans e.Result.
MODIFIER:
Ceci est généralement associé à WinForms et WPF, mais peut être utilisé par tout type d'application .NET. Voici un exemple de code pour une application console utilisant 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);
}
}
}
}
Sortie:
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
MISE À JOUR 2014
Voir la réponse de @ Roger ci-dessous.
https://stackoverflow.com/a/24916747/141172
Il fait remarquer que vous pouvez utiliser une tâche qui retourne un Task<T>
et cochez Task<T>.Result
.
Cela dépend de la manière dont vous voulez créer le fil et de la version .NET disponible:
.NET 2.0+:
A) Vous pouvez créer l'objet Thread
directement. Dans ce cas, vous pouvez utiliser "fermeture" - déclarer une variable et la capturer à l'aide de l'expression lambda:
object result = null;
Thread thread = new System.Threading.Thread(() => {
//Some work...
result = 42; });
thread.Start();
thread.Join();
Console.WriteLine(result);
B) Vous pouvez utiliser des délégués et IAsyncResult
et renvoyer la valeur de la méthode EndInvoke()
:
delegate object MyFunc();
...
MyFunc x = new MyFunc(() => {
//Some work...
return 42; });
IAsyncResult asyncResult = x.BeginInvoke(null, null);
object result = x.EndInvoke(asyncResult);
C) Vous pouvez utiliser BackgroundWorker
class. Dans ce cas, vous pouvez utiliser une variable capturée (comme avec l'objet Thread
) ou gérer l'événement 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+:
À partir de .NET 4.0, vous pouvez utiliser Bibliothèque de tâches parallèles et la classe Task
pour démarrer vos threads. La classe générique Task<TResult>
vous permet d'obtenir la valeur de retour de la propriété 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+:
À partir de .NET 4.5, vous pouvez également utiliser les mots clés async
/await
pour renvoyer directement la valeur d'une tâche au lieu d'obtenir la propriété Result
:
int result = await Task.Run(() => {
//Some work...
return 42; });
Remarque: la méthode, qui contient le code ci-dessus, devrait être marquée avec async
keyword.
Pour de nombreuses raisons, l'utilisation de Task Parallel Library est une méthode préférable pour travailler avec des threads.
Un thread n'est pas une méthode - vous ne "retournez" normalement pas de valeur.
Cependant, si vous essayez d'extraire une valeur des résultats de certains traitements, vous avez plusieurs options, les deux principales étant:
Cela dépend vraiment de la façon dont vous créez le fil et de la façon dont vous voulez l'utiliser, ainsi que du langage/framework/outils que vous utilisez.
Voici un exemple simple utilisant un délégué ...
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();
}
}
Il existe une série fantastique sur le threading à Threading in C # .
Ma classe préférée, exécute n'importe quelle méthode sur un autre thread avec seulement 2 lignes de code.
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);
}
}
usage
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);
}
Attention, longFunctionComplete NE sera PAS exécuté sur le même thread que starthework.
Pour les méthodes utilisant des paramètres, vous pouvez toujours utiliser des fermetures ou développer la classe.
Je suis tombé sur ce thread en essayant également d'obtenir la valeur de retour d'une méthode qui est exécutée dans un thread. Je pensais que je posterais ma solution qui fonctionne.
Cette solution utilise une classe pour stocker à la fois la méthode à exécuter (indirectement) et la valeur renvoyée. La classe peut être utilisée pour toute fonction et tout type de retour. Il vous suffit d'instancier l'objet à l'aide du type de valeur de retour, puis de transmettre la fonction à appeler via un lambda (ou un délégué).
Implémentation 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();
}
}
Pour utiliser ce code, vous pouvez utiliser un Lambda (ou un délégué). Voici l'exemple utilisant 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...
}
Implémentation VB.NET 2008
Toute personne utilisant VB.NET 2008 ne peut pas utiliser lambdas avec des méthodes de renvoi sans valeur. Ceci affecte la classe ThreadedMethod
, nous allons donc faire en sorte que ExecuteMethod
retourne la valeur de la fonction. Cela ne fait de mal à rien.
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
Avec le dernier .NET Framework, il est possible de renvoyer une valeur à partir d'un thread séparé à l'aide d'une tâche, où la propriété Result bloque le thread appelant jusqu'à la fin de la tâche:
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;
Pour plus de détails, consultez http://msdn.Microsoft.com/en-us/library/dd537613(v=vs.110).aspx
Si vous ne souhaitez pas utiliser un BackgroundWorker, mais simplement un Thread standard, vous pouvez déclencher un événement pour renvoyer des données comme ceci:
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
}
}
Les délégués ThreadStart en C # utilisés pour démarrer les threads ont le type de retour 'void'.
Si vous souhaitez obtenir une «valeur de retour» à partir d'un thread, vous devez écrire dans un emplacement partagé (de manière appropriée pour le thread) et en lire une fois l'exécution du thread terminée.
Utilisez simplement l'approche des délégués.
int val;
Thread thread = new Thread(() => { val = Multiply(1, 2); });
thread.Start();
Maintenant, créez la fonction Multiplier qui fonctionnera sur un autre thread:
int Multiply(int x, int y)
{
return x * y;
}
Le BackgroundWorker est agréable lors du développement pour Windows Forms.
Supposons que vous souhaitiez passer un cours simple en arrière:
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; }
}
J'ai écrit un court cours qui fait ce qui suit:
Depuis la routine du fil:
Ajouter un délégué peut être utile pour publier vos données directement dans votre thread principal, mais vous devrez peut-être utiliser Invoke si certains des éléments de données ne sont pas thread-safe.
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);
}
}
}
Pour tester cela, créez une petite application console et mettez-la dans le fichier 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);
}
}
}
Voici une capture d'écran de ce que j'ai obtenu avec ceci:
J'espère que les autres peuvent comprendre ce que j'ai essayé d'expliquer.
J'aime travailler sur des discussions et utiliser des délégués. Ils rendent C # très amusant.
Je voulais voir ce qui était impliqué dans l'écriture du code ci-dessus en tant qu'application VB Console. La conversion impliquait un certain nombre de choses auxquelles je ne m'attendais pas, je vais donc mettre à jour ce fil ici pour ceux qui veulent savoir comment utiliser le langage 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
Les threads n'ont pas vraiment de valeurs de retour. Toutefois, si vous créez un délégué, vous pouvez l'invoquer de manière asynchrone via la méthode BeginInvoke
. Cela exécutera la méthode sur un thread de pool de threads. Vous pouvez obtenir n'importe quelle valeur de retour telle que call via EndInvoke
.
Exemple:
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
sera exécuté sur un thread de pool de threads et une fois terminé, vous pourrez récupérer la réponse via EndInvoke
comme indiqué.
Une solution simple consiste à transmettre un paramètre par référence à la fonction en cours d'exécution dans le thread et à modifier sa valeur dans le thread.
// 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 vous ne pouvez pas changer la fonction en cours d'exécution dans la bande de roulement, vous pouvez l'envelopper d'une autre fonction:
bool theirFunction(var someVar){
return false;
}
void myFunction(var someVar ref bool result){
result = theirFunction(myVar);
}
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";
}
}
Peut utiliser ce code:
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;
}