从线程返回值?


Answers:


94

从线程获取返回值的最简单方法之一是使用闭包。创建一个变量,该变量将保存线程的返回值,然后在lambda表达式中捕获它。从工作线程中为该变量分配“返回”值,然后在该线程结束后就可以从父线程中使用它。

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
}

3
那岂不lock(value) { value = "Hello world"; }是在处理多线程值写入更好?
校验和

4
@checksum:在这种特殊情况下,没有必要,因为不会value同时发生任何读取或写入操作。但是,是的,请始终注意何时需要加锁。
Brian Gideon

很棒的主意!工作出色,应该是公认的答案。
MerseyViking

34

这取决于您要如何创建线程和可用的.NET版本:

.NET 2.0+:

A)您可以Thread直接创建对象。在这种情况下,您可以使用“ closure”-声明变量并使用lambda-expression捕获它:

object result = null;
Thread thread = new System.Threading.Thread(() => { 
    //Some work...
    result = 42; });
thread.Start();
thread.Join();
Console.WriteLine(result);

B)您可以使用委托和IAsyncResultEndInvoke()方法返回值:

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 LibraryTaskTask类来启动线程。泛型类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关键字。

由于许多原因,使用Task Parallel Library是使用线程的首选方法。


33

我会使用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更新

请参阅下面的@Roger答案。

https://stackoverflow.com/a/24916747/141172

他指出,您可以使用返回a的Task Task<T>并检查Task<T>.Result


是的,但是仅适用于WinForms和WPF。
亨克·霍尔特曼

@Henk:不对。我只是编写了一个简单的控制台应用程序,该应用程序使用BackgroundWorker来确保:-)使用该代码编辑了我的帖子。
Eric J.

埃里克(Eric),在您的代码中添加了一些代码行,以查看发生什么情况以及什么线程ID。它可能不会如您所愿。(完成将在Dowork完成之前运行,而不是在主线程上运行)。Bgw需要一个MessagePump。
汉克·霍尔特曼

@Henk:你说得对。Completed与BackgroundWorker在同一线程上运行,但是在DoWork完成之后它仍在运行。查看编辑答案中的输出。
Eric J.

2
没有竞争条件,因为只有一个线程设置变量,而只有一个线程读取变量,而设置与读取的确切顺序与代码的正确执行无关紧要(即终止条件可能在主线程中发生)稍早或稍晚,具体取决于线程的调度顺序,但是无论哪种方式,您仍然可以获得正确的结果)。
Eric J.

21

线程不是方法-通常不会“返回”值。

但是,如果您尝试从某些处理结果中获取值,则有很多选择,其中两个主要选择是:

  • 您可以同步共享的数据,并进行适当的设置。
  • 您还可以通过某种回调形式将数据传回。

这实际上取决于您如何创建线程,如何使用它以及所使用的语言/框架/工具。


15

我最喜欢的类,仅需两行代码即可在另一个线程上运行任何方法。

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相同的线程上执行。

对于带有参数的方法,您始终可以使用闭包或扩展类。


3
还不是所有人都清楚... stuffReturned?,resultOfWork,PrintWorkComplete吗?等
Lost_In_Library

14

这是一个使用委托的简单示例...

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#的Threading中,关于线程的系列非常棒。


9

只需使用委托方法。

int val;
Thread thread = new Thread(() => { val = Multiply(1, 2); });
thread.Start();

现在,使Multiply函数可以在另一个线程上运行:

int Multiply(int x, int y)
{
    return x * y;
}

3
为什么在“将其保存到文本文件并检索”下面的答案?
乔恩(Jon)

7

在尝试获取在Thread中执行的方法的返回值时,我遇到了该线程。我以为我会发布可行的解决方案。

此解决方案使用一个类来存储要间接执行的方法和返回值。该类可用于任何函数和任何返回类型。您只需使用返回值类型实例化对象,然后将函数传递给通过lambda(或委托)进行调用。


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(或委托)。这是使用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实施


使用VB.NET 2008的任何人都不能将lambda与非值返回方法一起使用。这会影响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

7

使用最新的.NET Framework,可以使用Task从一个单独的线程中返回一个值,其中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/zh-cn/library/dd537613(v=vs.110).aspx


5

用于启动线程的C#中的ThreadStart委托的返回类型为“ void”。

如果希望从线程中获取“返回值”,则应(以适当的线程安全方式)写入共享位置,并在线程完成执行后从该位置读取。


5

如果您不想使用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

    }
}

我在您的代码中修复了一个小细节。看起来您没有理会thread1_他连接AsyncCompletedEventHandler的那部分。如果我的编辑有误,请帮助我了解那里的情况。
jp2code 2015年

1
@ jp2code不能执行,thread1_Thread1Completed +=因为它thread1_Thread1Completed是函数的名称,因此不能将其放在赋值运算符的左侧。使用左侧Thread1Completed +=是因为它是一个事件,因此它可以出现在赋值运算符的左侧以添加事件处理程序。见public event AsyncCompletedEventHandler Thread1Completed;
AaronLS

我现在看到了。我不知道为什么#region以前在您的部分中看不到该事件处理程序。我看了。诚实!:)
jp2code 2015年

2

线程实际上没有返回值。但是,如果创建委托,则可以通过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如图所示检索答案。


2

为Windows窗体开发时,BackgroundWorker非常好。

假设您想来回传递一个简单的类:

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编码器

我想看看上面作为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

1
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()";}。在这种情况下,独立线程将没有时间为returnValue变量分配新值。作为最后的选择,您可以保存一个线程引用var standaloneThread = new Thread(()=> //...);,然后以同步方式启动它standaloneThread.Start(); standaloneThread.Join();。但这当然不是最佳实践。
AlexMelw

1

一个简单的解决方案是将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);
 }

请解释一下投票。我在自己的代码中使用了这种模式,并且效果很好。
CodeToad 2014年

0

可以使用此代码:

 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;
      }

-1

我不是线程专家,这就是为什么我这样做:

我创建了一个设置文件,

在新线程内:

Setting.Default.ValueToBeSaved;
Setting.Default.Save();

然后,我会在需要时选择该值。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.