我以前从未在C#中使用线程,我需要有两个线程,以及主UI线程。基本上,我有以下几点。
public void StartTheActions()
{
//Starting thread 1....
Thread t1 = new Thread(new ThreadStart(action1));
t1.Start();
// Now, I want for the main thread (which is calling `StartTheActions` method)
// to wait for `t1` to finish. I've created an event in `action1` for this.
// The I wish `t2` to start...
Thread t2 = new Thread(new ThreadStart(action2));
t2.Start();
}
所以,基本上,我的问题是如何让一个线程等待另一个线程完成。这样做的最佳方式是什么?
答案 0 :(得分:251)
我可以看到5个选项:
与米奇的回答一样。但这会阻止你的UI线程,但是你会为你内置一个Timeout。
WaitHandle
ManualResetEvent
是WaitHandle
。
需要注意的一件事是,如果您想等待多个线程,WaitHandle.WaitAll()
默认情况下不起作用,因为它需要一个MTA线程。你可以通过用Main()
标记你的MTAThread
方法来解决这个问题 - 但是这会阻止你的消息泵,不推荐我阅读的内容。
有关事件和多线程的信息,请参阅this page by Jon Skeet,事件可能会在if
和EventName(this,EventArgs.Empty)
之间取消订阅 - 以前发生在我身上。
(希望这些编译,我还没试过)
public class Form1 : Form
{
int _count;
void ButtonClick(object sender, EventArgs e)
{
ThreadWorker worker = new ThreadWorker();
worker.ThreadDone += HandleThreadDone;
Thread thread1 = new Thread(worker.Run);
thread1.Start();
_count = 1;
}
void HandleThreadDone(object sender, EventArgs e)
{
// You should get the idea this is just an example
if (_count == 1)
{
ThreadWorker worker = new ThreadWorker();
worker.ThreadDone += HandleThreadDone;
Thread thread2 = new Thread(worker.Run);
thread2.Start();
_count++;
}
}
class ThreadWorker
{
public event EventHandler ThreadDone;
public void Run()
{
// Do a task
if (ThreadDone != null)
ThreadDone(this, EventArgs.Empty);
}
}
}
public class Form1 : Form
{
int _count;
void ButtonClick(object sender, EventArgs e)
{
ThreadWorker worker = new ThreadWorker();
Thread thread1 = new Thread(worker.Run);
thread1.Start(HandleThreadDone);
_count = 1;
}
void HandleThreadDone()
{
// As before - just a simple example
if (_count == 1)
{
ThreadWorker worker = new ThreadWorker();
Thread thread2 = new Thread(worker.Run);
thread2.Start(HandleThreadDone);
_count++;
}
}
class ThreadWorker
{
// Switch to your favourite Action<T> or Func<T>
public void Run(object state)
{
// Do a task
Action completeAction = (Action)state;
completeAction.Invoke();
}
}
}
如果您使用_count方法,使用
增加它可能是一个想法(为了安全起见) Interlocked.Increment(ref _count)
我有兴趣知道使用委托和事件进行线程通知之间的区别,我知道的唯一区别是事件是同步调用的。
this question的答案可以清楚地描述您使用此方法的选项。
事件/委托的处理方式意味着您的事件处理程序方法位于thread1 / thread2 而不是主UI线程,因此您需要切换回正确在HandleThreadDone方法的顶部:
// Delegate example
if (InvokeRequired)
{
Invoke(new Action(HandleThreadDone));
return;
}
答案 1 :(得分:51)
添加
t1.Join(); // Wait until thread t1 finishes
启动之后,但这不会有太大成效,因为它与在主线程上运行的结果基本相同!
如果你想了解.NET中的线程,我强烈推荐阅读Joe Albahari的Threading in C#免费电子书。
答案 2 :(得分:30)
前两个答案很棒,适用于简单的场景。但是,还有其他方法可以同步线程。以下内容也适用:
public void StartTheActions()
{
ManualResetEvent syncEvent = new ManualResetEvent(false);
Thread t1 = new Thread(
() =>
{
// Do some work...
syncEvent.Set();
}
);
t1.Start();
Thread t2 = new Thread(
() =>
{
syncEvent.WaitOne();
// Do some work...
}
);
t2.Start();
}
ManualResetEvent是.NET框架提供的各种WaitHandle之一。它们可以提供比简单但非常常见的工具(如lock()/ Monitor,Thread.Join等)更丰富的线程同步功能。它们还可以用于同步两个以上的线程,允许复杂的场景,如“主”线程协调多个“子”线程,多个并发进程依赖于彼此的几个阶段进行同步等。
答案 3 :(得分:5)
您需要Thread.Join()
方法,或其中一个overloads。
答案 4 :(得分:4)
我希望你的主线程将回调方法传递给你的第一个线程,当它完成时,它将调用mainthread上的回调方法,该方法可以启动第二个线程。这样可以防止主线程在等待Join或Waithandle时挂起。无论如何,将方法作为委托传递是一个有用的东西。
答案 5 :(得分:1)
当我希望UI在等待任务完成时能够更新其显示时,我使用while循环在线程上测试IsAlive:
Thread t = new Thread(() => someMethod(parameters));
t.Start();
while (t.IsAlive)
{
Thread.Sleep(500);
Application.DoEvents();
}
答案 6 :(得分:0)
发布可能会帮助其他人,花费相当多的时间寻找像我想出的解决方案。所以我采取了一种不同的方法。上面有一个计数器选项,我只是采用了不同的方式。我正在旋转多个线程并递增计数器并在线程启动和停止时递减计数器。然后在main方法中,我想要暂停并等待线程完成,我做了。
while (threadCounter > 0)
{
Thread.Sleep(500); //Make it pause for half second so that we don’t spin the cpu out of control.
}
在我的博客上记录。 http://www.adamthings.com/post/2012/07/11/ensure-threads-have-finished-before-method-continues-in-c/
答案 7 :(得分:-1)
试试这个:
List<Thread> myThreads = new List<Thread>();
foreach (Thread curThread in myThreads)
{
curThread.Start();
}
foreach (Thread curThread in myThreads)
{
curThread.Join();
}
答案 8 :(得分:-1)
这是一个简单的示例,在同一个类中等待踩完。它还会调用同一名称空间中的另一个类。我包括了“ using”语句,因此只要您创建button1,它就可以作为Windows窗体窗体执行。
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
namespace ClassCrossCall
{
public partial class Form1 : Form
{
int number = 0; // This is an intentional problem, included
// for demonstration purposes
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
button1.Text = "Initialized";
}
private void button1_Click(object sender, EventArgs e)
{
button1.Text = "Clicked";
button1.Refresh();
Thread.Sleep(400);
List<Task> taskList = new List<Task>();
taskList.Add(Task.Factory.StartNew(() => update_thread(2000)));
taskList.Add(Task.Factory.StartNew(() => update_thread(4000)));
Task.WaitAll(taskList.ToArray());
worker.update_button(this, number);
}
public void update_thread(int ms)
{
// It's important to check the scope of all variables
number = ms; // This could be either 2000 or 4000. Race condition.
Thread.Sleep(ms);
}
}
class worker
{
public static void update_button(Form1 form, int number)
{
form.button1.Text = $"{number}";
}
}
}