スレッドから戻り値を取得する最も簡単な方法の1つは、クロージャーを使用することです。スレッドからの戻り値を保持する変数を作成し、ラムダ式でキャプチャします。ワーカースレッドからこの変数に「戻り」値を割り当て、そのスレッドが終了すると、親スレッドからそれを使用できます。
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
}
BackgroundWorker アプローチを使用して、e.Resultで結果を返します。
編集:
これは一般にWinFormsおよびWPFに関連付けられていますが、あらゆるタイプの.NETアプリケーションで使用できます。 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);
}
}
}
}
出力:
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
以下の@Rogerの回答を参照してください。
https://stackoverflow.com/a/24916747/141172
彼は、Task<T>
を返すタスクを使用して、Task<T>.Result
をチェックできることを指摘しています。
スレッドと使用可能な.NETバージョンをどのように作成するかによって異なります。
。NET 2.0 +:
A)Thread
オブジェクトを直接作成できます。この場合、「クロージャ」を使用できます-変数を宣言し、lambda-expressionを使用してキャプチャします。
object result = null;
Thread thread = new System.Threading.Thread(() => {
//Some work...
result = 42; });
thread.Start();
thread.Join();
Console.WriteLine(result);
B)デリゲートとIAsyncResult
を使用し、EndInvoke()
メソッドから値を返すことができます。
delegate object MyFunc();
...
MyFunc x = new MyFunc(() => {
//Some work...
return 42; });
IAsyncResult asyncResult = x.BeginInvoke(null, null);
object result = x.EndInvoke(asyncResult);
C)BackgroundWorker
クラスを使用できます。この場合、キャプチャされた変数(Thread
オブジェクトなど)を使用するか、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 +:
.NET 4.0以降では、 Task Parallel Library およびTask
クラスを使用してスレッドを開始できました。ジェネリッククラスTask<TResult>
を使用すると、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 +:
.NET 4.5以降では、async
プロパティを取得する代わりに、await
/Result
キーワードを使用してタスクから直接値を返すこともできます。
int result = await Task.Run(() => {
//Some work...
return 42; });
注:上記のコードを含むメソッドには、async
keywordのマークを付ける必要があります。
多くの理由から、タスク並列ライブラリの使用はスレッドを操作する好ましい方法です。
スレッドはメソッドではありません-通常、値を「返す」ことはありません。
ただし、何らかの処理の結果から値を取得しようとする場合、多くのオプションがあります。主な2つのオプションは次のとおりです。
- 共有データの一部を同期し、適切に設定できます。
- 何らかの形式のコールバックでデータを戻すこともできます。
スレッドの作成方法、使用方法、使用している言語/フレームワーク/ツールによって異なります。
これはデリゲートを使用した簡単な例です...
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#のスレッド化 には、スレッド化に関するすばらしいシリーズがあります。
私のお気に入りのクラスは、わずか2行のコードで別のスレッドで任意のメソッドを実行します。
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);
}
}
使用法
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);
}
longFunctionCompleteはstartheworkと同じスレッドで実行されないことに注意してください。
パラメータを取るメソッドの場合は、常にクロージャを使用するか、クラスを展開できます。
最新の.NET Frameworkでは、タスクを使用して別のスレッドから値を返すことができます。タスクが完了するまで、Resultプロパティは呼び出しスレッドをブロックします。
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;
詳細については、 http://msdn.Microsoft.com/en-us/library/dd537613(v = vs.110).aspx をご覧ください。
スレッド内で実行されるメソッドの戻り値も取得しようとしたときに、このスレッドに出会いました。動作するソリューションを投稿すると思いました。
このソリューションでは、クラスを使用して、実行されるメソッドを(間接的に)保存し、戻り値を保存します。このクラスは、すべての関数と戻り値の型に使用できます。戻り値の型を使用してオブジェクトをインスタンス化し、ラムダ(またはデリゲート)を介して呼び出す関数を渡します。
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();
}
}
このコードを使用するには、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...
}
VB.NET 2008の実装
VB.NET 2008を使用している人は、値を返さないメソッドでラムダを使用できません。これはThreadedMethod
クラスに影響するため、ExecuteMethod
が関数の値を返すようにします。これは何も傷つけません。
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
BackgroundWorkerを使用しないで、通常のスレッドを使用する場合は、次のようなデータを返すイベントを発生させることができます。
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
}
}
スレッドを開始するために使用されるC#のThreadStartデリゲートの戻り値の型は「void」です。
スレッドから「戻り値」を取得する場合は、共有場所に(適切なスレッドセーフな方法で)書き込み、スレッドの実行が完了したらそこから読み取る必要があります。
デリゲートアプローチを使用するだけです。
int val;
Thread thread = new Thread(() => { val = Multiply(1, 2); });
thread.Start();
次に、別のスレッドで機能するMultiply関数を作成します。
int Multiply(int x, int y)
{
return x * y;
}
BackgroundWorker は、Windowsフォーム用に開発する場合に便利です。
単純なクラスをやり取りしたいとします。
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; }
}
次のことを行う短いクラスを作成しました。
- リストを作成または消去する
- ループを開始する
- ループで、リストの新しいアイテムを作成します
- ループで、スレッドを作成します
- ループ内で、アイテムをパラメーターとしてスレッドに送信します
- ループで、スレッドを開始します
- ループ内で、監視するリストにスレッドを追加します
- ループの後、各スレッドに参加します
- すべての結合が完了したら、結果を表示します
スレッドルーチン内から:
- 一度に1つのスレッドのみがこのルーチンに入ることができるようにロックを呼び出します(他のスレッドは待つ必要があります)
- アイテムに関する情報を投稿します。
- アイテムを変更します。
- スレッドが完了すると、データがコンソールに表示されます。
デリゲート を追加すると、メインスレッドに直接データをポストバックするのに役立ちますが、一部のデータアイテムがスレッドセーフでない場合は Invoke を使用する必要があります。
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);
}
}
}
これをテストするには、小さなコンソールアプリケーションを作成し、これを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);
}
}
}
これは私がこれで得たもののスクリーンショットです:
私が説明しようとしたことを他の人が理解できることを願っています。
私はスレッドの作業とデリゲートの使用を楽しんでいます。彼らはC#をとても楽しくします。
付録:VB Codersの場合
上記のコードをVBコンソールアプリケーションとして記述することに何が関係しているかを知りたかったのです。変換には、私が予期していなかったいくつかのことが関係していたので、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
スレッドには実際には戻り値がありません。ただし、デリゲートを作成する場合は、BeginInvoke
メソッドを介して非同期的に呼び出すことができます。これにより、スレッドプールスレッドでメソッドが実行されます。 EndInvoke
を介した呼び出しなどから戻り値を取得できます。
例:
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
はスレッドプールスレッドで実行され、完了すると、図のようにEndInvoke
を介して回答を取得できます。
簡単な解決策は、refでスレッドで実行されている関数にパラメーターを渡し、スレッドでその値を変更することです。
// 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
}
トレッドで実行中の関数を変更できない場合は、別の関数をラップできます。
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";
}
}
このコードを使用できます:
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;
}