代码之家  ›  专栏  ›  技术社区  ›  Ian

异步POST+回调真的阻止排队吗?

  •  1
  • Ian  · 技术社区  · 16 年前

    我最近尝试实现异步回调模型,这样当开发人员使用我的库时,他们就不需要担心以下问题:

    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
    }
    

    当我走过的时候,我接到一个重复的电话 更新进度 但是,从已发布的事件中调用,因此看起来他们无法跟上进度,仍在发布中。处理方式如下:

    // 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测试应用程序看到的相同结果。

    这是否意味着这篇文章太慢了?我想我真正喜欢的是每次打电话 更新进度 如果是这种情况,我希望通过post清除队列中的任何现有post。但我不确定这是否可能?如果是这样,是否可以只为这些事件清除,因为我不想这样清除我的 更新数据库 意外事件…

    编辑

    一些缺失的方法,如果这样更容易的话。

    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 回复  |  直到 16 年前
        1
  •  1
  •   Hans Passant    16 年前

    现在还不清楚这里发生了什么。在UI线程上为工作线程上的一个updateProgress调用获取多个updateProgress调用绝对是错误的。您发布的代码不应该这样做。

    但是,是的,post()调用不是特别快。假设用户界面线程上的消息循环不忙于做其他事情,那么接收通知大约需要一毫秒的时间。这不是CPU工作的一毫秒,而是由处理getMessage()的Windows管道引起的延迟。线程上下文切换是它的一部分,但只是一小部分。

    这会产生明显的不良副作用。过于频繁地调用post(),或者让已发布的委托进行过多的艰苦工作,都会严重影响UI线程。到了不再逃避常规职责的地步。由于延迟,你可以很快到达那里;每秒只需要1000个帖子。

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

    总之,最好将它留给客户机代码来告诉您它希望如何对其通知进行封送。FileSystemWatcher.SynchroningObject就是一个很好的例子。如果post()崩溃,这是客户端代码修复问题的一种方法。还可以看一看BackgroundWorker类。

        2
  •  0
  •   Jim Mischel    16 年前

    您的示例中是否缺少一些代码?你打电话给 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
            }
        }
    }