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

Windows窗体线程和事件-传递事件的最有效方法?

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

    我的窗体接收来自随机工作线程上另一个对象的异步回调。我一直在使用下面所示的委托将数据传递给主线程(在主线程中,它可以用于更新屏幕控件)。性能是可怕的——一旦我达到每秒500次更新,程序就会完全锁定。我的GUI处理本身不是问题,因为我可以在表单中模拟这种级别的更新,并且没有问题。我是否应该使用更有效的机制将数据从一个线程传递到另一个线程?

    delegate void DStatus( MyStatus obj );
    DStatus _status; // set to MainThreadOnStatus during construction
    
    // this function only called on form's owner thread
    void MainThreadOnStatus( MyStatus obj )
    {
       // screen updates here as needed
    }
    
    // this function called by arbitrary worker threads in external facility
    void OnStatus( MyStatus obj )
    {
       this.BeginInvoke( _status, obj );
    }
    
    4 回复  |  直到 15 年前
        1
  •  1
  •   community wiki Ilya Ryzhenkov    16 年前

    您可能不需要在每个事件上更新UI,而是“不需要像每秒X次那样频繁”。您可以使用秒表或其他计时系统来收集一段时间内的事件,然后在适当的时候更新UI。

    如果需要捕获所有事件,请在队列中收集它们并每隔一段时间激发一次事件,并且该事件处理程序将处理队列并为所有排队事件更新一次UI。

        2
  •  2
  •   Ali    16 年前

    我对计时器不太感兴趣,如果您希望采用更为事件驱动的方法,请尝试以下方法:

    public class Foo
    {
        private AsyncOperation _asyncOperation = null;
        private SendOrPostCallback _notifyNewItem = null;
    
        //Make sure you call this on your UI thread.
        //Alternatively you can call something like the AttachUI() below later on and catch-up with
        //your workers later.
        public Foo()
        {
            this._notifyNewItem = new SendOrPostCallback(this.NewDataInTempList);
            this._asyncOperation = AsyncOperationManager.CreateOperation(this);
        }
    
        public void AttachUI()
        {
            if (this._asyncOperation != null)
            {
                this._asyncOperation.OperationCompleted();
                this._asyncOperation = null;
            }
    
            this._asyncOperation = AsyncOperationManager.CreateOperation(this);
            //This is for catching up with the workers if they’ve been busy already
            if (this._asyncOperation != null)
            {
                this._asyncOperation.Post(this._notifyNewItem, null);
            }
        }
    
    
        private int _tempCapacity = 500;
        private object _tempListLock = new object();
        private List<MyStatus> _tempList = null;
    
        //This gets called on the worker threads..
        //Keeps adding to the same list until UI grabs it, then create a new one.
        public void Add(MyStatus status)
        {
            bool notify = false;
            lock (_tempListLock)
            {
                if (this._tempList == null)
                {
                    this._tempList = new List<MyStatus>(this._tempCapacity);
                    notify = true;
                }
    
                this._tempList.Add(status);
            }
            if (notify)
            {
                if (this._asyncOperation != null)
                {
                    this._asyncOperation.Post(this._notifyNewItem, null);
                }
            }
        }
    
        //This gets called on your UI thread.
        private void NewDataInTempList(object o)
        {
            List<MyStatus> statusList = null;
            lock (this._tempListLock)
            {
                //Grab the list, and release the lock as soon as possible.
                statusList = this._tempList;
                this._tempList = null;
            }
            if (statusList != null)
            {
                //Deal with it here at your leasure
            }
        }
    }
    

    我在一个定制的log4net日志记录器中使用了这个方法,收集日志条目并将它们添加到一个绑定到网格的循环数组中。结果表现很好。

        3
  •  1
  •   Andrew Queisser    16 年前

    我一直按照伊利亚的建议做。对于不需要“实时”响应的统计研究所,我有一个秒表,每秒转两次左右。为了更快地更新,我使用一个队列或其他数据结构来存储事件数据,然后使用“lock(queue)”来避免争用。如果您不想减慢工作线程的速度,您必须确保UI线程不会阻塞工作线程太长时间。

        4
  •  0
  •   Brian ONeil    15 年前

    很难说出确切的问题,但有些可能性…

    是你的 MyStatus 传递给从中派生的OnStatus的对象 MarshalByRefObject (还有里面的每一个物体)?如果不是这样,它将在每一个被封送的调用上进行血清化,这可能会造成巨大的性能损失。

    另外,你真的应该打电话 this.InvokeRequired 在使用控件调用委托之前,实际上这只是一个最佳实践。