.NET BackgroundWorker RunWorkerAsync() 奇怪地被调用了两次

.NET BackgroundWorker RunWorkerAsync() oddly gets called twice

已更新代码以反映答案:同样的问题仍然出现

这个class应该运行列表中的所有任务,休眠然后醒来,无限重复这个过程。但出于某种原因,在第一次睡眠后,sleepThread.RunWorkerAsync() 调用由于某种原因被调用了两次。我显然可以通过以下方式解决这个问题:

if (!sleepThread.IsBusy) { sleepThread.RunWorkerAsync(); }

但这感觉像是一种解决方法。

这里是主要例程class:

public class ServiceRoutine
{
    private static volatile ServiceRoutine instance;
    private static object instanceLock = new object();
    private static object listLock = new object();
    private static readonly List<Task> taskList = new List<Task>()
    {
        new UpdateWaferQueueTask(),
        new UpdateCommentsTask(),
        new UpdateFromTestDataTask(),
        new UpdateFromTestStationLogsTask(),
        new UpdateFromWatchmanLogsTask(),
        new UpdateStationsStatusTask()
    };

    private List<Task> runningTasks;
    private BackgroundWorker sleepThread;
    private Logger log;

    private ServiceRoutine()
    {
        log = new Logger();
        runningTasks = new List<Task>();

        sleepThread = new BackgroundWorker();
        sleepThread.WorkerReportsProgress = false;
        sleepThread.WorkerSupportsCancellation = false;
        sleepThread.DoWork += (sender, e) =>
        {
            int sleepTime = ConfigReader.Instance.GetSleepTime();
            log.Log(Logger.LogType.Info, 
                "service sleeping for " + sleepTime / 1000 + " seconds");
            Thread.Sleep(sleepTime);
        };
        sleepThread.RunWorkerCompleted += (sender, e) => { Run(); };
    }
    public static ServiceRoutine Instance
    {
        get
        {
            if (instance == null)
            {
                lock (instanceLock)
                {
                    if (instance == null)
                    {
                        instance = new ServiceRoutine();
                    }
                }
            }
            return instance;
        }
    }

    public void Run()
    {
        foreach (Task task in taskList)
        {
            lock (listLock) 
            {
                runningTasks.Add(task);
                task.TaskComplete += (completedTask) =>
                {
                    runningTasks.Remove(completedTask);
                    if (runningTasks.Count <= 0)
                    {
                        sleepThread.RunWorkerAsync();
                    }
                };
                task.Execute();
            }
        }
    }
}

这是这样称呼的:

ServiceRoutine.Instance.Run();

来自服务启动方法。这也是任务 class:

public abstract class Task
{
    private Logger log;
    protected BackgroundWorker thread;

    public delegate void TaskPointer(Task task);
    public TaskPointer TaskComplete;

    public Task()
    {
        log = new Logger();
        thread = new BackgroundWorker();
        thread.WorkerReportsProgress = false;
        thread.DoWork += WorkLoad;
        thread.RunWorkerCompleted += FinalizeTask;
    }

    protected abstract string Name { get; }
    protected abstract void WorkLoad(object sender, DoWorkEventArgs e);

    private string GetInnerMostException(Exception ex)
    {
        string innerMostExceptionMessage = string.Empty;
        if (ex.InnerException == null) { innerMostExceptionMessage = ex.Message; }
        else
        {
            while (ex.InnerException != null)
            {
                innerMostExceptionMessage = ex.InnerException.Message;
            }
        }
        return innerMostExceptionMessage;
    }
    protected void FinalizeTask(object sender, RunWorkerCompletedEventArgs e)
    {
        try
        {
            if (e.Error != null)
            {
                string errorMessage = GetInnerMostException(e.Error);
                log.Log(Logger.LogType.Error, this.Name + " failed: " + errorMessage);
            }
            else
            {
                log.Log(Logger.LogType.Info, "command complete: " + this.Name);
            }
        }
        catch (Exception ex)
        {
            string errorMessage = GetInnerMostException(ex);
            log.Log(Logger.LogType.Error, this.Name + " failed: " + errorMessage);
        }
        finally { TaskComplete(this); }
    }
    public void Execute()
    {
        log.Log(Logger.LogType.Info, "starting: " + this.Name);
        thread.RunWorkerAsync();
    }
}

问题是,为什么 sleepThread.RunWorkerAsync() 被调用了两次,有没有更好的方法来完成这项工作,而无需在调用它之前检查线程是否忙?

您在这里面临竞争条件。问题出在 TaskComplete 回调中。在执行 if 条件之前,最后两个任务将自己从 runningTasks 列表中删除。执行时,列表计数为零。您应该在更改列表之前锁定列表。需要在TaskComplete回调中取锁:

runningTasks.Add(task);
task.TaskComplete += (completedTask) =>
{
    lock (runningTasks) 
    {
        runningTasks.Remove(completedTask);
        if (runningTasks.Count <= 0)
        {
            sleepThread.RunWorkerAsync();
        }
    }
};
task.Execute();

已解决

我在 runningTasks 列表上尝试了几种不同的锁定技术,但没有任何效果。将 runningTasks 更改为 BlockingCollection 后,一切正常。

这是使用 BlockingCollection 而不是 List 的新 add/remove 实现:

foreach (Task task in taskList)
{
    runningTasks.Add(task);
    task.TaskComplete += (completedTask) =>
    {
        runningTasks.TryTake(out completedTask);
        if (runningTasks.Count <= 0 && completedTask != null)
        {
            sleepThread.RunWorkerAsync();
        }
    };
    task.Execute();
}