Wie gebe ich einen Wert aus einem Thread zurück?
Eine der einfachsten Methoden, um einen Rückgabewert von einem Thread zu erhalten, besteht darin, Schließungen zu verwenden. Erstellen Sie eine Variable, die den Rückgabewert des Threads enthält, und erfassen Sie ihn dann in einem Lambda-Ausdruck. Weisen Sie der Variablen den "return" -Wert aus dem Arbeitsthread zu. Sobald dieser Thread beendet ist, können Sie ihn aus dem übergeordneten Thread verwenden.
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
}
Ich würde den BackgroundWorker -Ansatz verwenden und das Ergebnis in e.Result zurückgeben.
BEARBEITEN:
Dies ist in der Regel mit WinForms und WPF verbunden, kann jedoch von jeder Art von .NET-Anwendung verwendet werden. Beispielcode für eine Konsolenanwendung, die BackgroundWorker verwendet:
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);
}
}
}
}
Ausgabe:
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
2014 UPDATE
Siehe @ Rogers Antwort unten.
https://stackoverflow.com/a/24916747/141172
Er weist darauf hin, dass Sie eine Task verwenden können, die einen Task<T>
zurückgibt, und Task<T>.Result
überprüfen.
Es hängt davon ab, wie Sie den Thread und die verfügbare .NET-Version erstellen möchten:
.NET 2.0+:
A) Sie können das Thread
-Objekt direkt erstellen. In diesem Fall können Sie "close" verwenden - Variable deklarieren und mit Lambda-Ausdruck erfassen:
object result = null;
Thread thread = new System.Threading.Thread(() => {
//Some work...
result = 42; });
thread.Start();
thread.Join();
Console.WriteLine(result);
B) Sie können Delegaten und IAsyncResult
verwenden und den Wert der EndInvoke()
-Methode zurückgeben:
delegate object MyFunc();
...
MyFunc x = new MyFunc(() => {
//Some work...
return 42; });
IAsyncResult asyncResult = x.BeginInvoke(null, null);
object result = x.EndInvoke(asyncResult);
C) Sie können die Klasse BackgroundWorker
verwenden. In diesem Fall können Sie eine erfasste Variable (wie bei einem Objekt Thread
) oder ein Ereignis RunWorkerCompleted
verwenden:
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+:
Ab .NET 4.0 können Sie die Task Parallel Library - und Task
-Klasse zum Starten Ihrer Threads verwenden. Mit der generischen Klasse Task<TResult>
können Sie den Rückgabewert von der Result
-Eigenschaft abrufen:
//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+:
Ab .NET 4.5 können Sie auch async
/await
-Schlüsselwörter verwenden, um den Wert direkt aus der Aufgabe zurückzugeben, anstatt die Result
-Eigenschaft abzurufen:
int result = await Task.Run(() => {
//Some work...
return 42; });
Hinweis: Die Methode, die den obigen Code enthält, sollte mit async
keyword gekennzeichnet werden.
Aus vielen Gründen ist die Verwendung von Task Parallel Library die bevorzugte Methode, um mit Threads zu arbeiten.
Ein Thread ist keine Methode - normalerweise geben Sie keinen Wert zurück.
Wenn Sie jedoch versuchen, aus den Ergebnissen einiger Verarbeitungsvorgänge einen Wert abzurufen, haben Sie viele Möglichkeiten. Die zwei wichtigsten sind:
Es hängt wirklich davon ab, wie Sie den Thread erstellen und wie Sie ihn verwenden möchten, sowie von der verwendeten Sprache/Rahmenwerkzeuge.
Hier ist ein einfaches Beispiel mit einem Delegierten ...
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();
}
}
Es gibt eine großartige Serie zum Einfädeln bei Einfädeln in C # .
Meine Lieblingsklasse führt jede Methode in einem anderen Thread mit nur zwei Zeilen Code aus.
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);
}
}
verwendungszweck
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);
}
Beachten Sie, dass longFunctionComplete NICHT in demselben Thread ausgeführt wird wie starthework.
Für Methoden, die Parameter verwenden, können Sie immer Schließungen verwenden oder die Klasse erweitern.
Ich bin auf diesen Thread gestoßen, als ich auch versuchte, den Rückgabewert einer Methode zu erhalten, die innerhalb eines Threads ausgeführt wird. Ich dachte, ich würde meine Lösung posten, die funktioniert.
Diese Lösung verwendet eine Klasse, um sowohl die auszuführende Methode (indirekt) als auch den Rückgabewert zu speichern. Die Klasse kann für jede Funktion und jeden Rückgabetyp verwendet werden. Sie instanziieren das Objekt einfach mit dem Rückgabewerttyp und übergeben die Funktion, um sie über ein Lambda (oder einen Delegaten) aufzurufen.
C # 3.0-Implementierung
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();
}
}
Um diesen Code zu verwenden, können Sie einen Lambda (oder einen Delegierten) verwenden. Hier ist das Beispiel mit 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...
}
VB.NET 2008-Implementierung
Jeder, der VB.NET 2008 verwendet, kann keine Lambdas mit Methoden ohne Wertrückgabe verwenden. Dies wirkt sich auf die ThreadedMethod
-Klasse aus, sodass wir ExecuteMethod
den Wert der Funktion zurückgeben. Das tut nichts weh.
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
Mit dem neuesten .NET Framework ist es möglich, einen Wert mit einem Task aus einem separaten Thread zurückzugeben. Die Result-Eigenschaft blockiert den aufrufenden Thread, bis der Task abgeschlossen ist:
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;
Weitere Informationen finden Sie unter http://msdn.Microsoft.com/de-de/library/dd537613(v=vs.110).aspx
Wenn Sie keinen BackgroundWorker und nur einen normalen Thread verwenden möchten, können Sie ein Ereignis auslösen, um Daten wie folgt zurückzugeben:
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
}
}
ThreadStart-Delegaten in C #, die zum Starten von Threads verwendet werden, haben den Rückgabetyp 'void'.
Wenn Sie einen 'Rückgabewert' von einem Thread abrufen möchten, sollten Sie an einen freigegebenen Speicherort schreiben (in einer geeigneten Thread-sicheren Weise) und aus diesem lesen, wenn der Thread die Ausführung abgeschlossen hat.
Verwenden Sie einfach den Delegiertenansatz.
int val;
Thread thread = new Thread(() => { val = Multiply(1, 2); });
thread.Start();
Jetzt machen Sie die Multiply-Funktion, die in einem anderen Thread funktionieren wird:
int Multiply(int x, int y)
{
return x * y;
}
Der BackgroundWorker ist nett, wenn er für Windows Forms entwickelt wird.
Angenommen, Sie wollten eine einfache Klasse hin und her führen:
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; }
}
Ich habe eine kurze Klasse geschrieben, die Folgendes tut:
Aus der Thread-Routine heraus:
Das Hinzufügen eines delegate kann hilfreich sein, um Ihre Daten direkt in Ihrem Haupt-Thread zu veröffentlichen. Möglicherweise müssen Sie Invoke verwenden, wenn einige Datenelemente nicht threadsicher sind.
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);
}
}
}
Um dies zu testen, erstellen Sie eine kleine Konsolenanwendung und fügen Sie diese in die Datei Program.cs ein:
// 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);
}
}
}
Hier ist ein Screenshot von dem, was ich damit habe:
Ich hoffe, dass andere verstehen können, was ich zu erklären versuchte.
Ich arbeite gerne an Threads und benutze Delegierte. Sie machen C # sehr viel Spaß.
Ich wollte sehen, was mit dem Schreiben des obigen Codes als VB - Konsolenanwendung verbunden war. Die Konvertierung beinhaltete einige Dinge, die ich nicht erwartet hatte. Daher werde ich diesen Thread hier für alle aktualisieren, die wissen möchten, wie man in VB einen Thread erstellt.
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
Threads haben eigentlich keine Rückgabewerte. Wenn Sie jedoch einen Delegaten erstellen, können Sie ihn asynchron über die BeginInvoke
-Methode aufrufen. Dadurch wird die Methode in einem Threadpool-Thread ausgeführt. Sie können einen beliebigen Rückgabewert von beispielsweise Aufruf über EndInvoke
erhalten.
Beispiel:
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
wird in einem Thread-Pool-Thread ausgeführt, und wenn Sie fertig sind, können Sie die Antwort wie gezeigt über EndInvoke
abrufen.
Eine einfache Lösung besteht darin, einen Parameter per ref an die Funktion zu übergeben, die im Thread ausgeführt wird, und seinen Wert im Thread zu ändern.
// 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
}
Wenn Sie die in der Lauffläche ausgeführte Funktion nicht ändern können, können Sie eine andere Funktion einpacken:
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";
}
}
Kann diesen Code verwenden:
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;
}