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

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

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

注意事项:

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

我的问题是:

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

感谢您的帮助。

【问题讨论】:

  • 您无法通知线程。您可以通知一个对象。更好地描述这部分,你可能会得到更好的答案。

标签: c# multithreading synchronization


【解决方案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 接口。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-29
      • 2012-02-19
      • 1970-01-01
      • 2017-01-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多