【问题标题】:.NET BackgroundWorker RunWorkerAsync() oddly gets called twice.NET BackgroundWorker RunWorkerAsync() 奇怪地被调用了两次
【发布时间】:2015-02-24 21:16:04
【问题描述】:

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

这个类应该运行列表中的所有任务,休眠然后唤醒并无限重复该过程。但是出于某种原因,在第一次睡眠之后, 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() 会被调用两次,有没有更好的方法来完成这项工作,而无需在调用之前检查线程是否忙?

【问题讨论】:

    标签: c# .net asynchronous service backgroundworker


    【解决方案1】:

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

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

    【讨论】:

    • 我创建了一个私有静态对象 listLock 对象然后 lock(listLock) { runningTasks.Remove(completedTask); },这仍然会导致同样的问题。
    • P.S.我刚刚更新了代码示例。我是否错误地锁定了列表?
    • 不,这仍然不起作用。我已经通过使用 BlockingCollection 而不是 List 解决了它;我会发布我的答案。
    【解决方案2】:

    已解决

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-01-19
      • 2018-03-20
      • 2017-01-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多