1

我遇到了一个烦人的问题,主要是因为我在 C# 多线程方面的技能水平/经验低。

这是背景。 在我的框架中WaitFormHelper,我有一个Start()静态类名为a (这是一个带有自定义消息和进度条的小型加载控件)Close()Start()lockerWaitForm

在我当前的项目中,我有一个方法,它启动 a 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()and方法,也简化了:Close()

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为真(即您可能已经理解,由于某种原因,我无法执行计算,例如“用户在我的表单中输入废话”),执行直接关闭 my WaitForm. 但是在这种情况下,主线程将在方法触发的线程之前Close到达该方法并获取locker对象上的锁。Start()

发生的情况是:Close获取锁,尝试关闭表单但instance仍然为空,因为Thread创建的InitializeCallerThread仍在等待锁实际创建它。Close释放锁,InitializeCallerThread获取它并...显示一个WaitForm不会关闭的。

现在我知道我可以通过在实际启动 WaitForm 之前测试计算参数是否无效来简单地解决这个问题,但是问题是WaitForm我们框架的所有应用程序都应该使用它(其中包括 40 多个不同的应用程序使用和在 4 个国家/地区维护),因此理想情况下,我宁愿WaitForm在所有情况下都看到我的工作。

您对我如何同步它以确保肯定首先调用并执行启动线程有任何想法吗?如您所见,我已经AutoResetEvent为此使用了 an ,但是如果我在测试前听它,if (instance != null)我最终会陷入困境。

希望这足够清楚!谢谢!

4

2 回答 2

1

您需要一些机制来进行“队列控制”,根据您希望它们发生的顺序协调步骤。

最近我需要实现这样的东西来强制执行单元测试中多个线程的特定顺序。

这是我的建议:

队列同步器:

/// <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();
    }
}
于 2012-07-25T01:55:45.743 回答
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();
                }));
        }
    }
}
于 2012-07-24T16:36:12.540 回答