1

C# マルチスレッドのスキル レベル/経験が低いことが主な原因で、ちょっと厄介な問題が発生しています。

これが背景です。私のフレームワークには、 という名前の静的クラスがあり、これには 2 つの静的メソッドがあります (実際にはもっと多くのことですが、ここでは気にしWaitFormHelperません)。 このメソッドは、オブジェクトのロックを取得して作成するスレッドを初期化して開始します。 a (これは、カスタム メッセージとプログレス バーを備えた小さな読み込みコントロールです)Start()Close()Start()lockerWaitForm

現在のプロジェクトでは、 を開始し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()

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ます。 が true の場合 (つまり、おそらく既にご存じのとおり、「ユーザーがフォームにがらくたを入力する」などの何らかの理由で計算を実行できません)、実行によって my が直接閉じられますWaitForm。ただし、この場合、メイン スレッドはメソッドに到達し、スレッドがメソッドによって起動されるにオブジェクトCloseのロックを取得します。lockerStart()

Closeロックを取得し、フォームを閉じようとしますが、フォームを閉じようとしますが、まだinstancenull です。これは、Threadcreated inInitializeCallerThreadが実際にロックを作成するのを待っているためです。Closeロックを解除し、InitializeCallerThreadそれを取得して...WaitForm閉じない a を示します。

WaitForm を実際に開始する前に計算パラメーターが無効かどうかをテストすることで、この問題を簡単に修正できることがわかりましたが、問題は、これWaitFormがフレームワークのすべてのアプリケーションで使用されることになっていることです (これには、使用される 40 以上の異なるアプリが含まれ、 4 か国で維持されている) ため、理想的にはWaitForm、すべてのケースで私の作業を確認したいと考えています。

スタータースレッドが最初に確実に呼び出されて実行されるように、これを同期する方法について何か考えはありますか? ご覧のとおりAutoResetEvent、この件に関しては既に を使用していますが、テストの前にそれを聞くとif (instance != null)、私の場合はスタックしてしまいます。

これが十分に明確であることを願っています!ありがとうございました!

4

2 に答える 2

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();
    }
}
于 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 に答える