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

时间:2015-02-24 21:16:05

标签: c# .net asynchronous service backgroundworker

更新代码反映回答:同样问题仍然存在

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

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

但这感觉就像一个解决方案。

这是主要的例行课程:

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();

来自服务启动方法。这也是Task类:

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()会被调用两次,是否有更好的方法可以在调用之前检查线程是否忙碌而无法完成此工作?

2 个答案:

答案 0 :(得分:1)

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

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

答案 1 :(得分:0)

<强>解决

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

这是使用BlockingCollection而不是List的新添加/删除实现:

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();
}