async Post +回调真的可以阻止排队吗?

时间:2010-02-16 17:31:44

标签: c# asynchronous

我最近尝试实现异步回调模型,因此当开发人员使用我的库时,他们不需要担心以下内容:

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);
}

2 个答案:

答案 0 :(得分:1)

目前尚不清楚这里发生了什么。在UI线程上为工作线程上的一个UpdateProgress调用获取多个UpdateProgress调用肯定是坏事。您发布的代码不应该这样做。

但是,是的,Post()调用并不是特别快。假设它没有忙于做其他事情,UI线程上的消息循环需要大约一毫秒才能获取通知。这不是一毫秒的硬CPU工作,它是由处理GetMessage()的Windows管道引起的延迟。线程上下文切换是其中的一部分,但只是一小部分。

这会产生明显的令人讨厌的副作用。经常调用Post(),或者让发布的委托做太多努力,会严重影响UI线程。到了它不再能够解决它的常规职责。由于延迟,你可以快速到达那里;它每秒只需要1000个帖子。

当然,没有必要经常发布,人眼无法以超过每秒25次的速度感知更新。更频繁地做这件事只是浪费精力。但是,获得这种理想的更新速率并不是特别容易实现,除非工作线程特别注意经过的时间。

Anyhoo,最好将它留给客户端代码告诉你它希望如何封送通知。 FileSystemWatcher.SynchronizingObject就是一个很好的例子。如果Post()崩溃,这是客户端代码解决问题的一种方法。另请参阅BackgroundWorker类。

答案 1 :(得分:0)

您的示例中是否缺少某些代码?您可以调用UpdateProgress(this, e),但没有相应的方法使用该签名。除非thisGuid,但我对此表示怀疑,因为那样你最终会得到一个无限递归的方法。您也可以拨打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
        }
    }
}