1

我正在编写一个与第 3 方程序交互的程序。这个第 3 方程序允许用户制作可以运行在第 3 方程序中进行的步骤记录的按钮。但!这些按钮还可以运行用户定义的批处理文件。因此,我使用此功能通过创建文件并检查这些文件是否存在来与我的程序进行交互。

我的程序由两个类组成,一个 Actionlistener 和一个 Actionperformer。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 作为参数运行程序并读取记录器输出时,我应该得到如下信息:

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]

我第二次调用 CheckForActions,一切都按预期工作......

有谁知道发生了什么?

4

3 回答 3

6

问题不在于Enum.GetValues,而在于您传递值的方式。

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

这个创建了新的闭包,其中包含对变量 action 的引用(因此,当“action”值发生变化时,线程会看到新值。

您可以将“动作”作为参数传递给线程。

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 回答