同步线程 - 没有 UI

Posted

技术标签:

【中文标题】同步线程 - 没有 UI【英文标题】:synchronize threads - no UI 【发布时间】:2011-01-28 15:19:48 【问题描述】:

我正在尝试编写多线程代码并面临一些同步问题。我知道这里有很多帖子,但我找不到合适的。

我有一个System.Timers.Timer,它每 30 秒经过一次,它会进入数据库并检查是否有任何新作业。如果他找到一个,他会在当前线程上执行作业(计时器为每个经过的新线程打开)。在作业运行时,我需要将进度通知主线程(计时器所在的位置)。

注意事项:

    我没有 UI,所以我不能像通常在 winforms 中那样做beginInvoke(或使用后台线程)。 我想在我的主课上实现ISynchronizeInvoke,但这看起来有点矫枉过正(也许我错了)。 我的作业类中有一个事件,主类注册到它,我会在需要时调用该事件,但我担心它可能会导致阻塞。 每个作业最多可能需要 20 分钟。 我最多可以同时运行 20 个作业。

我的问题是:

在我的工作线程中通知我的主线程任何进展的正确方法是什么?

感谢您的帮助。

【问题讨论】:

您无法通知线程。您可以通知一个对象。更好地描述这部分,你可能会得到更好的答案。 【参考方案1】:

您还可以使用lock 来实现线程安全的JobManager 类,该类跟踪不同工作线程的进度。在此示例中,我只维护活动工作线程数,但这可以扩展到您的进度报告需求。

class JobManager

    private object synchObject = new object();

    private int _ActiveJobCount;

    public int ActiveJobsCount
    
        get  lock (this.synchObject)  return _ActiveJobCount;  
        set  lock (this.synchObject)  _ActiveJobCount = value;  
    

    public void Start(Action job)
    
        var timer = new System.Timers.Timer(1000);

        timer.Elapsed += (sender, e) =>
        
            this.ActiveJobsCount++;
            job();
            this.ActiveJobsCount--;
        ;

        timer.Start();
    

例子:

class Program

    public static void Main(string[] args)
    
        var manager = new JobManager();

        manager.Start(() => Thread.Sleep(3500));

        while (true)
        
            Console.WriteLine(manager.ActiveJobsCount);

            Thread.Sleep(250);
        
    

【讨论】:

【参考方案2】:

可以通过回调方法通知主线程进度。那就是:

// in the main thread
public void ProgressCallback(int jobNumber, int status)

    // handle notification

您可以在调用它时将该回调方法传递给工作线程(即作为委托),或者工作线程的代码可以隐式“知道”它。无论哪种方式都有效。

jobNumber 和 status 参数只是示例。您可能希望使用其他方式来识别正在运行的作业,并且您可能希望对状态使用枚举类型。无论您如何操作,请注意 ProgressCallback 将由多个线程同时调用,因此如果您要更新任何共享数据结构或写入日志信息,则必须使用锁或其他同步技术保护这些资源。

您也可以为此使用事件,但保持主线程的事件订阅是最新的可能是一个潜在的问题。如果您忘记从特定工作线程的事件中取消订阅主线程,您也有可能发生内存泄漏。虽然事件肯定会起作用,但我会推荐这个应用程序的回调。

【讨论】:

吉姆,我认为这是同步调用,而我需要异步调用 进度回调在工作线程上调用,而不是在主线程上。所以从主线程的角度来看,这是一个异步调用。【参考方案3】:

使用事件。例如,BackgroundWorker 类是专为您的想法而设计的。

http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

ReportProgress 函数和 ProgressChanged 事件是您将用于进度更新的函数。

pullJobTimer.Elapsed += (sender,e) =>

    BackgroundWorker worker = new BackgroundWorker();
    worker.WorkerReportsProgress = true;
    worker.DoWork += (s,e) =>
    
        // Whatever tasks you want to do
        // worker.ReportProgress(percentComplete);
    ;
    worker.ProgressChanged += mainThread.ProgressChangedEventHandler;
    worker.RunWorkerAsync();
;

【讨论】:

我觉得使用BackgroundWorker是错误的,我可以解释原因。 System.Timers.Timer pullJobTimer = new System.Timers.Timer(INTERVAL); pullJobTimer.Elapsed += new System.Timers.ElapsedEventHandler(PullQJobs);我可以在 pulljobs 中打开一个新的 BackgroundWorker,这是真的,然后我将拥有 ProgressChanged。但是计时器过时会打开一个新线程,然后我会打开一个新的后台工作线程???感觉不对吧?或者也许我没有明白你的想法。 您甚至不必打开一个新线程。当您通过 RunWorkerAsync() 异步运行它时,BackgroundWorker 会为您执行此操作。正如 Jim Mischel 所建议的那样,这也不会出现内存泄漏,因为事件订阅在被垃圾收集时会被 BackgroundWorker 实例破坏。 我知道后台工作人员打开了一个线程,但也打开了计时器。所以主类使用 Timer -> Timer 为每个经过的线程打开一个新线程 -> 然后我运行 backgroundworker 打开另一个线程。这就是让我感到不舒服的原因。 还要注意ProgressChanged 事件处理程序将在线程池工作线程中运行,而不是在应用程序主线程上。 @Jake, "System.Timers.Timer 类默认情况下会在从公共语言运行时 (CLR) 线程池获得的工作线程上调用您的计时器事件处理程序。"【参考方案4】:

如果您不介意依赖 .NET 3.0,您可以使用 Dispatcher 在线程之间编组请求。它的行为方式与 Windows 窗体中的 Control.Invoke() 类似,但没有窗体依赖项。不过,您需要添加对 WindowsBase 程序集的引用(.NET 3.0 和更高版本的一部分,并且是 WPF 的基础)

如果您不能依赖 .NET 3.0,那么我会说您从一开始就采用了正确的解决方案:在您的主类中实现 ISynchronizeInvoke 接口并将其传递给SynchronizingObject 定时器的属性。然后您的计时器回调将在主线程上调用,然后可以生成BackgroundWorkers 检查数据库并运行任何排队的作业。这些作业将通过ProgressChanged 事件报告进度,该事件将自动编组对主线程的调用。

快速的谷歌搜索揭示了this example 如何实际实现 ISynchronizeInvoke 接口。

【讨论】:

以上是关于同步线程 - 没有 UI的主要内容,如果未能解决你的问题,请参考以下文章

qt 线程与ui线程同步

同步测试线程、UI线程和CursorLoader线程

访问 List 项时 UI 和 Worker 线程同步

线程UI同步

在 UI 线程上同步取消挂起的任务

jQuery同步Ajax带来的UI线程阻塞问题及解决办法