我有一个恼人的问题,主要是因为我在C#多线程方面的技能水平较低/经验不足。
这是背景。在我的框架中,我有一个名为WaitFormHelper
的静态类,它有两个静态方法(嗯......实际上更多但我们在这里并不关心),Start()
和Close()
Start()
方法初始化并启动一个线程,该线程将获取locker
对象上的锁并创建WaitForm
(这是一个带有自定义消息和进度条的小型加载控件)< / p>
在我当前的项目中,我有一个方法,它启动WaitForm
,执行计算,然后关闭WaitForm
。没什么好看的。
该方法如下所示,我尽可能地简化了它:
public void PerformCalculations()
{
try
{
WaitFormHelper.Start("Title", "message", false);
if (this.CalculationsParameters.IsInvalid)
{
return;
}
// Perform all those lengthy calculations here
}
// catch whatever exception I may have to catch, we don't care here
finally
{
WaitFormHelper.Close();
}
}
以下是Start()
和Close()
方法以及相关方法&amp;属性,也简化了:
private static Thread instanceCaller;
private static WaitForm instance;
private static AutoResetEvent waitFormStarted = new AutoResetEvent(false);
private static object locker = new object();
/// <summary>
/// Initializes WaitForm to start a single task
/// </summary>
/// <param name="header">WaitForm header</param>
/// <param name="message">Message displayed</param>
/// <param name="showProgressBar">True if we want a progress bar, else false</param>
public static void Start(string header, string message, bool showProgressBar)
{
InitializeCallerThread(showProgressBar, header, message);
instanceCaller.Start();
}
/// <summary>
/// Initializes caller thread for executing a single command
/// </summary>
/// <param name="showProgressBar"></param>
/// <param name="header"></param>
/// <param name="message"></param>
private static void InitializeCallerThread(bool showProgressBar, string header, string message)
{
waitFormStarted.Reset();
instanceCaller = new Thread(() =>
{
lock (locker)
{
instance = new WaitForm()
{
Header = header,
Message = message,
IsProgressBarVisible = showProgressBar
};
waitFormStarted.Set();
}
instance.ShowDialog();
});
instanceCaller.Name = "WaitForm thread";
instanceCaller.SetApartmentState(ApartmentState.STA);
instanceCaller.IsBackground = true;
}
/// <summary>
/// Closes current form
/// </summary>
public static void Close()
{
lock (locker)
{
if (instance != null && !instance.IsClosed)
{
waitFormStarted.WaitOne();
instance.FinalizeWork();
instance.Dispatcher.Invoke(
new Action(() =>
{
instance.Close();
}));
}
}
}
现在让我们来解决问题
这通常可以正常工作,除非在这种情况下:
如果this.CalculationsParameters.IsInvalid
为真(即,您可能已经理解,我无法出于某种原因执行计算,例如“用户在我的表单中输入垃圾”),执行将直接关闭WaitForm
。
但是在这种情况下,主线程将到达Close
方法并获取locker
对象之前锁定 Start()
方法触发的线程。< / p>
会发生什么:Close
获取锁定,尝试关闭表单,但instance
仍然为空,因为Thread
中创建的InitializeCallerThread
仍在等待锁定实际创建它。 Close
发布了锁定,InitializeCallerThread
获取了它,并且...显示了一个不会关闭的WaitForm
。
现在我知道我可以通过在实际启动WaitForm之前测试计算参数是否无效来解决这个问题,但问题是这个WaitForm
应该被我们框架的所有应用程序使用(其中包括在4个国家/地区使用和维护的40多个不同的应用程序),所以理想情况下我更愿意看到WaitForm
在所有情况下工作。
您是否知道如何同步这一点以确保首先调用并执行启动程序线程?
正如您所看到的,我已经使用AutoResetEvent
来解决此问题,但如果我在测试之前听取它if (instance != null)
,我将最终陷入困境。
希望这很清楚!谢谢!
答案 0 :(得分:1)
您需要一些机制来进行“队列控制”,根据您希望它们发生的顺序协调步骤。
最近我需要实现类似这样的操作,在单元测试中强制执行多个线程的特定顺序。
这是我的建议:
<强> QueueSynchronizer:强>
/// <summary>
/// Synchronizes steps between threads.
/// </summary>
public class QueueSynchronizer
{
/// <summary>
/// Constructor.
/// </summary>
/// <param name="minWait">Minimum waiting time until the next try.</param>
/// <param name="maxWait">Maximum waiting time until the next try.</param>
public QueueSynchronizer(Int32 minWait, Int32 maxWait)
{
}
private Mutex mx = new Mutex();
/// <summary>
/// Minimum waiting time until the next try.
/// </summary>
private Int32 minWait = 5;
/// <summary>
/// Maximum waiting time until the next try.
/// </summary>
private Int32 maxWait = 500;
int currentStep = 1;
/// <summary>
/// Key: order in the queue; Value: Time to wait.
/// </summary>
private Dictionary<int, int> waitingTimeForNextMap = new Dictionary<int, int>();
/// <summary>
/// Synchronizes by the order in the queue. It starts from 1. If is not
/// its turn, the thread waits for a moment, after that, it tries again,
/// and so on until its turn.
/// </summary>
/// <param name="orderInTheQueue">Order in the queue. It starts from 1.</param>
/// <returns>The <see cref="Mutex"/>The mutex that must be released at the end of turn.
/// </returns>
public Mutex Sincronize(int orderInTheQueue)
{
do
{
//while it is not the turn, the thread will stay in this loop and sleeping for 100, 200, ... 1000 ms
if (orderInTheQueue != this.currentStep)
{
//The next in queue will be waiting here (other threads).
mx.WaitOne();
mx.ReleaseMutex();
//Prevents 100% processing while the current step does not happen
if (!waitingTimeForNextMap.ContainsKey(orderInTheQueue))
{
waitingTimeForNextMap[orderInTheQueue] = this.minWait;
}
Thread.Sleep(waitingTimeForNextMap[orderInTheQueue]);
waitingTimeForNextMap[orderInTheQueue] = Math.Min(waitingTimeForNextMap[orderInTheQueue] * 2, this.maxWait);
}
} while (orderInTheQueue != this.currentStep);
mx.WaitOne();
currentStep++;
return mx;
}
}
Start()
和Close()
与QueueSynchronizer
:
//synchronizer
private static QueueSynchronizer queueSynchronizer;
private static Thread instanceCaller;
private static WaitForm instance;
private static AutoResetEvent waitFormStarted = new AutoResetEvent(false);
private static object locker = new object();
/// <summary>
/// Initializes WaitForm to start a single task
/// </summary>
/// <param name="header">WaitForm header</param>
/// <param name="message">Message displayed</param>
/// <param name="showProgressBar">True if we want a progress bar, else false</param>
public static void Start(string header, string message, bool showProgressBar)
{
queueSynchronizer = new QueueSynchronizer();
InitializeCallerThread(showProgressBar, header, message);
instanceCaller.Start();
}
/// <summary>
/// Initializes caller thread for executing a single command
/// </summary>
/// <param name="showProgressBar"></param>
/// <param name="header"></param>
/// <param name="message"></param>
private static void InitializeCallerThread(bool showProgressBar, string header, string message)
{
waitFormStarted.Reset();
instanceCaller = new Thread(() =>
{
lock (locker)
{
//Queuing to run on first.
Mutex mx = queueSynchronizer.Sincronize(1);
try
{
instance = new WaitForm()
{
Header = header,
Message = message,
IsProgressBarVisible = showProgressBar
};
}
finally
{
//I think is here that ends the first step!?
mx.ReleaseMutex();
}
waitFormStarted.Set();
}
instance.ShowDialog();
});
instanceCaller.Name = "WaitForm thread";
instanceCaller.SetApartmentState(ApartmentState.STA);
instanceCaller.IsBackground = true;
}
/// <summary>
/// Closes current form
/// </summary>
public static void Close()
{
//Queuing to run on second.
Mutex mx = queueSynchronizer.Sincronize(2);
try
{
lock (locker)
{
if (instance != null && !instance.IsClosed)
{
waitFormStarted.WaitOne();
instance.FinalizeWork();
instance.Dispatcher.Invoke(
new Action(() =>
{
instance.Close();
}));
}
}
}
finally
{
mx.ReleaseMutex();
}
}
答案 1 :(得分:0)
您需要加入您创建的主题。通过连接一个线程,你将在那个时阻塞,直到线程完成执行。
public static void Close()
{
lock (locker)
{
instanceCaller.Join();
if (instance != null && !instance.IsClosed)
{
waitFormStarted.WaitOne();
instance.FinalizeWork();
instance.Dispatcher.Invoke(
new Action(() =>
{
instance.Close();
}));
}
}
}