我最近尝试实现异步回调模型,因此当开发人员使用我的库时,他们不需要担心以下内容:
if(control.InvokeRequried) { // invoke }
这部分代码看起来效果很好,但我今天发现了一些有点奇怪的东西,我认为我已经诊断出来,但是想看看其他有更多经验的人会想到什么,以及我可能会怎么做解决它。
我遇到的问题是,由于某些原因,我的帖子似乎很慢,因此它们看起来有点堵塞,并在我完成工作后继续运行。一个简短的代码示例:
// called by user
WorkerEventHandler workerDelegate = new WorkerEventHandler(CalcAsync);
workerDelegate.BeginInvoke(p, asyncOp, null, null);
CalcAsync(P p, AsyncOperation asyncOp)
{
Calculate();
asyncOp.PostOperationCompleted(new AsyncCompletedEventArgs(null, false,
asyncOp.UserSuppliedState);
}
Calculate()
{
DoStuff() // updates the progress as it goes
this.UpdateStatus("Done"); // marks us as done
this.UpdateProgress(pId, 100); // increase progress to max
}
当单步执行时,我会重复调用从发布的事件调用的 UpdateProgress ,因此看起来他们无法跟上,并且仍在发布。这些处理如下:
// initalized with
// onProgressUpdatedDelegate = new SendOrPostCallback(UpdateProgress);
private SendOrPostCallback onProgressUpdatedDelegate;
public void UpdateProgress(Guid pId, ProgressEventArgs e)
{
AsyncOperation asyncOp = GetAsyncOperation(pId);
if (asyncOp != null)
{
asyncOp.Post(this.onProgressUpdatedDelegate, e);
}
else
{
UpdateProgress(this, e);
}
}
private void UpdateProgress(object state)
{
ProgressEventArgs e = state as ProgressEventArgs;
UpdateProgress(this, e); // does the actual triggering of the EventHandler
}
如果相关,这些事件都写入控制台。但我似乎只看到我的进度在较长的操作上下降,这看起来与我在WPF测试应用程序中看到的结果相同。
是否表明邮政只是缓慢?我想我真正喜欢的是每次我通过帖子调用 UpdateProgress 我想清除队列中的任何现有帖子(如果是这种情况)。但我不确定这是可能的吗?如果是,是否可以仅为这些事件进行清除,因为我不想意外清除 UpdateStatus 事件......
修改
如果这样做的一些缺失的方法更容易。
public void UpdateProgress(Guid pId, ProgressEventArgs e)
{
AsyncOperation asyncOp = GetAsyncOperation(pId);
if (asyncOp != null)
{
asyncOp.Post(this.onProgressUpdatedDelegate, e);
}
else
{
UpdateProgress(this, e);
}
}
private void UpdateProgress(object sender, ProgressEventArgs e)
{
ProgressChangedEventHandler handler;
lock (progressUpdateEventLock)
{
handler = progressUpdateEvent;
}
if (handler != null)
handler(sender, e);
}
答案 0 :(得分:1)
目前尚不清楚这里发生了什么。在UI线程上为工作线程上的一个UpdateProgress调用获取多个UpdateProgress调用肯定是坏事。您发布的代码不应该这样做。
但是,是的,Post()调用并不是特别快。假设它没有忙于做其他事情,UI线程上的消息循环需要大约一毫秒才能获取通知。这不是一毫秒的硬CPU工作,它是由处理GetMessage()的Windows管道引起的延迟。线程上下文切换是其中的一部分,但只是一小部分。
这会产生明显的令人讨厌的副作用。经常调用Post(),或者让发布的委托做太多努力,会严重影响UI线程。到了它不再能够解决它的常规职责。由于延迟,你可以快速到达那里;它每秒只需要1000个帖子。
当然,没有必要经常发布,人眼无法以超过每秒25次的速度感知更新。更频繁地做这件事只是浪费精力。但是,获得这种理想的更新速率并不是特别容易实现,除非工作线程特别注意经过的时间。
Anyhoo,最好将它留给客户端代码告诉你它希望如何封送通知。 FileSystemWatcher.SynchronizingObject就是一个很好的例子。如果Post()崩溃,这是客户端代码解决问题的一种方法。另请参阅BackgroundWorker类。
答案 1 :(得分:0)
您的示例中是否缺少某些代码?您可以调用UpdateProgress(this, e)
,但没有相应的方法使用该签名。除非this
是Guid
,但我对此表示怀疑,因为那样你最终会得到一个无限递归的方法。您也可以拨打UpdateProgress(100)
,但没有相应的方法。
无论如何,如果你使用了一个探查器,或者如果你没有一个Stopwatch
,可能会有所帮助,看看你的方法花费了多少时间。
可能是线程上下文切换正在杀死你,因为每次更新都必须InvokeRequired
然后Invoke
。如果是这种情况,将Post
放入队列并拥有一个定期清空队列的计时器可能会有所帮助。类似的东西:
Timer queueTimer = new Timer(QueueTimerProc, null, 20, 20);
queueTimer.Start();
void QueueTimerProc(object state)
{
if (queue.Count > 0)
{
if (this.InvokeRequired)
// Invoke the EmptyQueue method
else
// Call the EmptyQueue method
}
}
void EmptyQueue()
{
lock (queue)
{
while (queue.Count > 0)
{
// dequeue an item and post the update
}
}
}