Risposte:
Uno dei modi più semplici per ottenere un valore restituito da un thread è utilizzare le chiusure. Creare una variabile che conterrà il valore restituito dal thread e quindi acquisirlo in un'espressione lambda. Assegna il valore di "ritorno" a questa variabile dal thread di lavoro e quindi una volta che il thread termina puoi usarlo dal thread 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
}
value
vengono eseguite letture o scritture contemporaneamente. Ma sì, sii sempre consapevole di quando è necessario un lucchetto.
Dipende da come si desidera creare il thread e dalla versione .NET disponibile:
.NET 2.0+:
A) Puoi creare l' Thread
oggetto direttamente. In questo caso puoi usare "chiusura" - dichiara la variabile e catturala usando l'espressione lambda:
object result = null;
Thread thread = new System.Threading.Thread(() => {
//Some work...
result = 42; });
thread.Start();
thread.Join();
Console.WriteLine(result);
B) È possibile utilizzare delegati IAsyncResult
ee valore restituito dal EndInvoke()
metodo:
delegate object MyFunc();
...
MyFunc x = new MyFunc(() => {
//Some work...
return 42; });
IAsyncResult asyncResult = x.BeginInvoke(null, null);
object result = x.EndInvoke(asyncResult);
C) Puoi usare BackgroundWorker
class. In questo caso potresti usare la variabile catturata (come con l' Thread
oggetto) o gestire l' 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 partire da .NET 4.0 è possibile utilizzare Task Parallel Library e la Task
classe per avviare i thread. La classe generica Task<TResult>
ti consente di ottenere il valore restituito dalla Result
proprietà:
//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 partire da .NET 4.5 potresti anche usare async
/ await
keywords per restituire il valore direttamente dall'attività invece di ottenere la Result
proprietà:
int result = await Task.Run(() => {
//Some work...
return 42; });
Nota: il metodo, che contiene il codice sopra, deve essere contrassegnato con async
parola chiave.
Per molte ragioni l'utilizzo di Task Parallel Library è un modo preferibile di lavorare con i thread.
Vorrei utilizzare l' approccio BackgroundWorker e restituire il risultato in e.Result.
MODIFICARE:
Questo è comunemente associato a WinForms e WPF, ma può essere utilizzato da qualsiasi tipo di applicazione .NET. Ecco un esempio di codice per un'app console che utilizza 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);
}
}
}
}
Produzione:
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
AGGIORNAMENTO 2014
Vedi la risposta di @ Roger di seguito.
https://stackoverflow.com/a/24916747/141172
Sottolinea che puoi usare un'attività che restituisce a Task<T>
e controlla Task<T>.Result
.
Un thread non è un metodo: normalmente non "restituisci" un valore.
Tuttavia, se stai cercando di recuperare un valore dai risultati di alcune elaborazioni, hai molte opzioni, le due principali sono:
Dipende davvero da come stai creando il thread e da come vuoi usarlo, così come dal linguaggio / framework / strumenti che stai usando.
La mia classe preferita, esegue qualsiasi metodo su un altro thread con solo 2 righe di codice.
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);
}
}
utilizzo
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);
}
Attenzione che longFunctionComplete NON verrà eseguito sullo stesso thread di starthework.
Per i metodi che accettano parametri puoi sempre usare le chiusure o espandere la classe.
Ecco un semplice esempio che utilizza un delegato ...
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();
}
}
C'è una serie eccezionale sul threading in Threading in C # .
Usa semplicemente l'approccio delegato.
int val;
Thread thread = new Thread(() => { val = Multiply(1, 2); });
thread.Start();
Ora crea la funzione Moltiplica che funzionerà su un altro thread:
int Multiply(int x, int y)
{
return x * y;
}
Mi sono imbattuto in questo thread anche quando cercavo di ottenere il valore di ritorno di un metodo che viene eseguito all'interno di un thread. Ho pensato di pubblicare la mia soluzione che funziona.
Questa soluzione utilizza una classe per memorizzare sia il metodo da eseguire (indirettamente) sia per memorizzare il valore restituito. La classe può essere utilizzata per qualsiasi funzione e qualsiasi tipo restituito. È sufficiente creare un'istanza dell'oggetto utilizzando il tipo di valore restituito e quindi passare la funzione da chiamare tramite lambda (o delegato).
Implementazione di 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();
}
}
Per usare questo codice puoi usare un Lambda (o un delegato). Ecco l'esempio che utilizza lambda:
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...
}
Implementazione di VB.NET 2008
Chiunque utilizzi VB.NET 2008 non può utilizzare lambda con metodi di restituzione senza valore. Ciò influisce sulla ThreadedMethod
classe, quindi ExecuteMethod
restituiremo il valore della funzione. Questo non fa male a niente.
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 l'ultima versione di .NET Framework, è possibile restituire un valore da un thread separato utilizzando un'attività, in cui la proprietà Result blocca il thread chiamante fino al termine dell'attività:
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;
Per i dettagli, vedere http://msdn.microsoft.com/en-us/library/dd537613(v=vs.110).aspx
I delegati ThreadStart in C # usati per avviare i thread hanno il tipo restituito "void".
Se desideri ottenere un "valore di ritorno" da un thread, dovresti scrivere in una posizione condivisa (in un modo appropriato per thread-safe) e leggere da quella quando il thread ha completato l'esecuzione.
Se non vuoi usare un BackgroundWorker e usi solo un thread normale, puoi attivare un evento per restituire dati come questo:
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 del suo cablaggio di AsyncCompletedEventHandler . Se la mia modifica era sbagliata, aiutami a capire cosa stava succedendo lì.
thread1_Thread1Completed +=
perché thread1_Thread1Completed
è il nome di una funzione, quindi non puoi metterlo sul lato sinistro di un operatore di assegnazione. Il lato sinistro Thread1Completed +=
viene utilizzato perché si tratta di un evento, quindi può essere visualizzato sul lato sinistro dell'operatore di assegnazione per aggiungere gestori di eventi. Vederepublic event AsyncCompletedEventHandler Thread1Completed;
#region
sezione. Ho guardato. Onesto! :)
I thread non hanno realmente valori di ritorno. Tuttavia, se crei un delegato, puoi richiamarlo in modo asincrono tramite il BeginInvoke
metodo. Questo eseguirà il metodo su un thread del pool di thread. È possibile ottenere qualsiasi valore di ritorno da come call tramite EndInvoke
.
Esempio:
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
verrà eseguito su un thread del pool di thread e una volta completato è possibile recuperare la risposta tramite EndInvoke
come mostrato.
Il BackgroundWorker è bello quando in via di sviluppo per Windows Form.
Supponi di voler passare avanti e indietro una lezione semplice:
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; }
}
Ho scritto una breve lezione che fa quanto segue:
Dall'interno della routine thread:
L'aggiunta di un delegato può essere utile per inviare nuovamente i dati direttamente al thread principale, ma potrebbe essere necessario utilizzare Invoke se alcuni degli elementi di dati non sono 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);
}
}
}
Per verificarlo, crea una piccola applicazione console e inseriscila nel file 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);
}
}
}
Ecco uno screenshot di quello che ho ottenuto con questo:
Spero che gli altri possano capire quello che ho cercato di spiegare.
Mi piace lavorare sui thread e usare i delegati. Rendono C # molto divertente.
Volevo vedere cosa era coinvolto nella scrittura del codice sopra come applicazione console VB. La conversione ha coinvolto alcune cose che non mi aspettavo, quindi aggiornerò questo thread qui per coloro che vogliono sapere come eseguire il thread in 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()";}
. In questo caso il thread autonomo non avrebbe il tempo di assegnare un nuovo valore alla returnValue
variabile. Come ultima risorsa, puoi salvare un riferimento al thread var standaloneThread = new Thread(()=> //...);
e successivamente avviarlo in modo sincronizzato standaloneThread.Start(); standaloneThread.Join();
. Ma questa non è certamente la migliore pratica.
Una soluzione semplice è passare un parametro per ref alla funzione in esecuzione nel thread e modificarne il valore nel 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
}
Se non puoi modificare la funzione in esecuzione nel battistrada, puoi avvolgerla in un'altra funzione:
bool theirFunction(var someVar){
return false;
}
void myFunction(var someVar ref bool result){
result = theirFunction(myVar);
}
Può usare questo codice:
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"; }
sarebbe meglio gestire la scrittura di valori di thread multipli?