同步线程 - 没有 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的主要内容,如果未能解决你的问题,请参考以下文章