1

サードパーティのプログラムとやり取りするプログラムを書いています。このサードパーティ プログラムにより、ユーザーは、サードパーティ プログラムで作成されたステップの記録を実行できるボタンを作成できます。しかし!これらのボタンは、ユーザー定義のバッチ ファイルを実行することもできます。そのため、この機能を使用して、ファイルを作成し、これらのファイルが存在するかどうかを確認して、プログラムとやり取りします。

私のプログラムは、Actionlistener と Actionperformer の 2 つのクラスで構成されています。actionperformer には、可能なアクションの列挙が含まれています。

読み取り関数は次のようになります。

static public void CheckForActions()
{
    //For every action in the Enum
    foreach (ActionPerformer.PossibleActions action in Enum.GetValues(typeof(ActionPerformer.PossibleActions)))
    {
        //If a file exists with the same name as the current action
        if (File.Exists(_location + action.ToString()))
        {
            //Delete "message" and create a new thread to perform this action.
            File.Delete(_location + action);
            Thread _t = new Thread(() => 
            { 
                new ActionPerformer(action);
            });

            _t.SetApartmentState(ApartmentState.STA);
            _t.Start();

            //Add thread to list so they can be joined propperly
            _list.Add(_t);

            //Write information to log
            Logger.LogInfo(_t, "Starting new action: " + action.ToString(), DateTime.Now);
        }
    }

    //If there are items in the list
    if (_list.Count > 0)
    {
        //Dispose every thread once its done.
        foreach (Thread _t in _list)
        {
            _t.Join();
            Logger.LogInfo("Finishing action.", DateTime.Now);
        }
        _list.Clear();
    }
}

ActionPerformer クラスは次のようになります。

class ActionPerformer
{
    public enum PossibleActions
    {
        action1,
        action2,
    }

    public ActionPerformer(PossibleActions action)
    {
        Logger.LogInfo(action.ToString(), DateTime.Now);
    }
}

したがって、 action1 を parameter としてプログラムを実行し、ロガー出力を読み取ると、次のような結果が得られるはずです。

Starting new action: action1 [13:30:05]
action1 [13:30:05]
Finishing action. [13:30:05]

しかし、初めて CheckForActions を呼び出すと、常に次の出力が得られます。

Starting new action: action1 [13:30:05]
action2 [13:30:05] //Notice how it is not action1?
Finishing action. [13:30:05]

2回目にCheckForActionsを呼び出すと、すべてが期待どおりに機能します...

誰が何が起こっているのか知っていますか?

4

3 に答える 3

6

問題は ではなくEnum.GetValues、値を渡す方法にあります。

Thread _t = new Thread(() => 
{ 
     new ActionPerformer(action);
});

これは、変数 action への REFERENCE を含む新しいクロージャーを作成します (したがって、「アクション」の値が変更されると、スレッドは新しい値を認識します。

「アクション」をパラメーターとしてスレッドに渡すことができます。

Thread _t = new Thread((act) => 
{ 
     new ActionPerformer(act);
});
_t.Start(action);

または、他の人が提案したアプローチを使用します (foreach の本体にローカル変数を作成し、クロージャーでアクセスします)。

クロージャーで変更された変数にアクセスすると、Resharper が警告を発すると思います。

ところで、ローカル変数の前にアンダースコアを付けないでください。通常の c# 標準には従いません。

于 2013-06-10T11:41:00.760 に答える
2

あなたは閉鎖しています。

最も簡単な回避策: アクションのコピーを作成します。

foreach (ActionPerformer.PossibleActions action in Enum.GetValues(typeof(ActionPerformer.PossibleActions)))
{
    //If a file exists with the same name as the current action
    if (File.Exists(_location + action.ToString()))
    {
        var actionCopy = action;

        //Delete "message" and create a new thread to perform this action.
        File.Delete(_location + action);
        Thread _t = new Thread(() => 
        { 
            new ActionPerformer(actionCopy);
        });

        _t.SetApartmentState(ApartmentState.STA);
        _t.Start();

        //Add thread to list so they can be joined propperly
        _list.Add(_t);

        //Write information to log
        Logger.LogInfo(_t, "Starting new action: " + action.ToString(), DateTime.Now);
    }
}

アクションを ThreadStart(object) のパラメーターとして渡すこともできます

于 2013-06-10T11:41:34.217 に答える
1

次のように、スレッドが次の反復で開始される可能性があるため、ローカル変数を反復値に割り当てます。

foreach (ActionPerformer.PossibleActions action in Enum.GetValues(typeof(ActionPerformer.PossibleActions)))
{
   var localAction = action;

   // Use localAction instead of action from here-on in
   ...
}

これはマルチスレッドの「バグ」のように見えるため、.NET 4.5 で動作が修正され、開発者が期待する動作になります。

于 2013-06-10T11:40:46.953 に答える