65.9K
CodeProject 正在变化。 阅读更多。
Home

WPF 中的 I/O 操作

2009年7月29日

CPOL

2分钟阅读

viewsIcon

20375

downloadIcon

198

本文介绍了一种在 WPF 应用程序中将所有 I/O 操作运行在单个线程中的方法。

引言

本文介绍了一种在 WPF 应用程序中将所有 I/O 操作运行在单个线程中的方法。这在使用基于单线程上下文的数据存储库(例如 EntityFramework)时非常有用。

问题

作为 WPF 和 EntityFramework 的新手,我需要构建一个客户端应用程序来检索和插入数据库中的数据。由于数据库操作可能耗时,并且我不想阻塞客户端视图,我希望这些操作在不同的线程上运行。最初,我尝试使用 System.ComponentModel.BackgroundWorker,但发现 System.Data.Objects.ObjectContext 在单个线程上工作。这对我来说是一个挫折,我必须为这个问题想出一个优雅的解决方案。

提出的解决方案

我的解决方案是创建一个线程,该线程接受来自主线程的请求并将其返回给主线程。该线程由一个类管理,该类保存一个请求队列。

/// <summary>
/// Handle an event queue which is responisble
/// for running requests outside of the Main thread.
/// It would be usually used for running I/O operations
/// </summary>
public class UIQueueManager : IDisposable
{
    #region Constants
    /// <summary>
    /// Limit the operation duration to 10 seconds
    /// </summary>
    const int MAX_OPERATION_DURATION = 10000;

    #endregion

    #region Members

    /// <summary>
    /// The working thread in which the object would run in
    /// </summary>
    Thread m_workingThread;
    /// <summary>
    /// A thread which runs and makes sure that
    /// no request is withholding the system
    /// </summary>
    Thread m_terminationThread;
    /// <summary>
    /// A lock for queues enqueue and dequeue synchronization
    /// </summary>
    object m_lock;
    /// <summary>
    /// The item currently running
    /// </summary>
    KeyValuePair<Guid, IUIQueueItem> m_currentQueueItem;
    /// <summary>
    /// The queue itself
    /// </summary>
    System.Collections.Queue m_operationsQueue;
    /// <summary>
    /// A flag which indicates if it is OK to run the queue
    /// </summary>
    bool m_bRun;

    #endregion

    #region Properties

    #endregion

    #region Methods

    #region Ctor

    public UIQueueManager()
    {
        m_lock = new object();
        m_operationsQueue = new System.Collections.Queue();
        m_workingThread = new Thread(this.RunQueue);
        m_terminationThread = new Thread(this.CheckQueueValidity);
        m_currentQueueItem = new KeyValuePair<Guid, IUIQueueItem>();
        m_bRun = true;
        m_workingThread.Start();
        m_terminationThread.Start();
    }

    #endregion

    /// <summary>
    /// Add a queue item to the queue. The returned result is a key
    /// Which enables to remove the task from the queue
    /// </summary>
    /// <param name="item"></param>
    /// <returns></returns>
    public void AddToQueue(IUIQueueItem item)
    {
        Guid guid = Guid.NewGuid();

        try
        {
            Monitor.Enter(m_lock);
            m_operationsQueue.Enqueue(
              new KeyValuePair<Guid,IUIQueueItem>(guid, item));
        }
        catch
        {
            throw;
        }
        finally
        {
            Monitor.Exit(m_lock);
        }
        return;
    }

    /// <summary>
    /// Running the queue operation in a synchronos way
    /// </summary>
    public void RunQueue()
    {
        while (m_bRun)
        {
            if (m_operationsQueue.Count > 0)
            {
                KeyValuePair<Guid, IUIQueueItem> kvp = 
                  new KeyValuePair<Guid,IUIQueueItem>();
                try
                {
                    // Get a message out of the queue
                    try
                    {
                        Monitor.Enter(m_lock);

                        kvp = (KeyValuePair<Guid, 
                                 IUIQueueItem>)m_operationsQueue.Dequeue();
                        m_currentQueueItem = kvp;
                    }
                    catch(Exception ex)
                    {
                        SetItemError(kvp.Value, ex);
                    }
                    finally
                    {
                        Monitor.Exit(m_lock);
                    }


                    if (kvp.Value != null)
                    {
                        kvp.Value.Operation();
                    }
                }
                catch (Exception ex)
                {
                    SetItemError(kvp.Value, ex);
                }
                finally
                {
                    if (kvp.Value != null)
                    {
                        OperationEnded(kvp.Value);
                    }
                }
            }
            Thread.Sleep(1);
        }
    }

    /// <summary>
    /// This method runs on seperate thread.
    /// It checks that the thread has been
    /// Updated
    /// </summary>
    private void CheckQueueValidity()
    {
        while (m_bRun)
        {
            Guid guid = Guid.NewGuid();
            try
            {
                Monitor.Enter(m_lock);
                guid = m_currentQueueItem.Key;
            }
            catch
            {
            }
            finally
            {
                Monitor.Exit(m_lock);
            }
            Thread.Sleep(MAX_OPERATION_DURATION);
            if ((guid == m_currentQueueItem.Key) && (guid != Guid.Empty))
            {
                // The thread is in the same queue.
                // The operation should be terminated
                Monitor.Enter(m_lock);
                try
                {
                    m_workingThread.Abort();
                    // Mark the current operation as failed
                    // and send notification to the user
                    m_currentQueueItem.Value.IsSuccesful = false;
                    m_currentQueueItem.Value.ErrorDescription = 
                                    "Operation timed out";
                    OperationEnded(m_currentQueueItem.Value);
                    m_workingThread = new Thread(this.RunQueue);
                    m_workingThread.Start();
                    OperationEnded(m_currentQueueItem.Value);
                }
                catch
                {
                }
                finally
                {
                    Monitor.Exit(m_lock);
                }
            }
        }
    }

    /// <summary>
    /// Set the error notification in case of exception
    /// </summary>
    /// <param name="item"></param>
    /// <param name="ex"></param>
    private void SetItemError(IUIQueueItem item, Exception ex)
    {
        if (item != null)
        {
            item.IsSuccesful = false;
            item.ErrorDescription = ex.Message;
        }
    }

    /// <summary>
    /// Run the operation end. If it runs under UI mode use the begin invoke.
    /// Otherwise, run it normaly.
    /// </summary>
    /// <param name="item"></param>
    private void OperationEnded(IUIQueueItem item)
    {
        if (System.Windows.Application.Current != null)
        {
            System.Windows.Application.Current.Dispatcher.BeginInvoke(
                          new ThreadStart(item.OperationCompleted));
        }
        else
        {
            item.OperationCompleted();
        }
        return;
    }

   #region IDisposable Members

    public void Dispose()
    {
        Monitor.Enter(m_lock);
        if (m_workingThread != null)
        {
            m_workingThread.Abort();
        }
        if (m_terminationThread != null)
        {
            m_terminationThread.Abort();
        }
        m_bRun = false;

        Monitor.Exit(m_lock);
    }

    #endregion

    #endregion

}

该类保存两个线程。一个用于处理所有请求,另一个用于监视第一个线程并验证请求是否“卡”在队列中。

请求队列项实现一个简单的接口

/// <summary>
/// An interface for UI queue item
/// </summary>
public interface IUIQueueItem
{
    #region Properties

    /// <summary>
    /// A flag whcih determines if the operation succeeded 
    /// </summary>
    bool IsSuccesful
    {
        get;
        set;
    }

    /// <summary>
    /// A string description of the operations error
    /// </summary>
    string ErrorDescription
    {
        get;
        set;
    }

    #endregion

    #region Methods

    /// <summary>
    /// The main opeartion which runs on the queue
    /// </summary> 
    void Operation();
    /// <summary>
    /// An address to return the callback
    /// </summary>
    void OperationCompleted();

    #endregion

}

该接口设置为指示操作是否成功完成,并在出现故障时提供描述。

我从该接口继承以创建一个可以处理调用的通用类

/// <summary>
/// This is a generic UI queue item which gets
/// 2 delegates as parameters and handles them
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="U"></typeparam>    
public class UIQueueItem<T,U> : IUIQueueItem
{
    #region Delegates
    /// <summary>
    /// The base type which is ran by the requester
    /// </summary>
    /// <param name="val"></param>
    /// <returns></returns>
    public delegate T GenericRequester(U val);
    /// <summary>
    /// The generic type which is ran as a response
    /// </summary>
    /// <param name="val"></param>
    public delegate void GenericHandler(T val);

    #endregion

    #region Members

    bool m_bSuccesful;
    string m_strErrorDescription;

    /// <summary>
    /// The delegate which would be ran in the operation
    /// </summary>
    GenericRequester m_GenericRequester;
    /// <summary>
    /// The delegate which would be ran as the response
    /// </summary>
    GenericHandler m_GenericHandler;
    /// <summary>
    /// The parameter for the request
    /// </summary>
    U m_Param;
    /// <summary>
    /// The responses value
    /// </summary>
    T m_Response;

    #endregion

    #region Methods

    #region Ctor

    /// <summary>
    /// The Ctor is initiated with 2 delegates
    /// and a parameter for the delegates
    /// </summary>
    /// <param name="request"></param>
    /// <param name="handler"></param>
    /// <param name="param"></param>
    public UIQueueItem(GenericRequester request,
                       GenericHandler handler,
                       U param)
    {
        m_bSuccesful = true;
        m_strErrorDescription = string.Empty;
        m_GenericRequester = request;
        m_GenericHandler = handler;
        m_Param = param;            
    }

    #endregion

    #region IUIQueueItem Members

    public bool IsSuccesful
    {
        get
        {
            return m_bSuccesful;
        }
        set
        {
            m_bSuccesful = value;
        }
    }

    public string ErrorDescription
    {
        get
        {
            return m_strErrorDescription;
        }
        set
        {
            m_strErrorDescription = value;
        }
    }

    public void Operation()
    {
        m_Response = m_GenericRequester(m_Param);
    }

    public void OperationCompleted()
    {
        m_GenericHandler(m_Response);
    }

    #endregion

    #endregion
}

使用代码

我的示例应用程序基于我在 Modeling MVVM 中的工作。我正在运行一个控制台应用程序作为服务器,以及一个显示树状视图的 WPF 客户端应用程序。同时运行这两个应用程序。浏览树。您可以看到每个节点都会启动对服务器的调用。

这是通过使用 IViewModel 机制完成的。每次节点获得焦点时,都会弹出一个事件。该事件路由到事件处理程序类,该类将请求转换为 UIQueue

/// <summary>
/// This class handles the UI Tasks.
/// </summary>
public class UITasksHandler
{
    #region Members

    /// <summary>
    /// A dictionary which holds delegates for handling the events
    /// </summary>
    Dictionary<ViewModelOperationsEnum, 
      WorldView.ViewModel.MainViewModel.VMEventHandler> m_EventHandler;
    UIQueueManager m_UIQueueManager;

    #endregion

    #region Methods

    #region Ctor

    public UITasksHandler(UIQueueManager uiQueueManager)
    {
        m_EventHandler = new Dictionary<ViewModelOperationsEnum, 
                               MainViewModel.VMEventHandler>();
        m_UIQueueManager = uiQueueManager;
        InitEventHandlers();
    }

    private void InitEventHandlers()
    {            
        m_EventHandler.Add(ViewModelOperationsEnum.GetContinentSize, 
                           this.GetContinentSize);
        m_EventHandler.Add(ViewModelOperationsEnum.GetCountryCurrency, 
                           this.GetCounrtyCurrency);
        m_EventHandler.Add(ViewModelOperationsEnum.GetCityTemprature, 
                           this.GetCityTemprature);
    }

    #endregion

    public void HandleEvent(IViewModel sender, object param, 
                            ViewModelOperationsEnum viewModelEvent)
    {
        WorldView.ViewModel.MainViewModel.VMEventHandler handler;
        if (m_EventHandler.TryGetValue(viewModelEvent, out handler))
        {
            handler(sender, param);
        }
    }

    private void GetContinentSize(IViewModel sender, object param)
    {
        IContinentViewModel vm = sender as IContinentViewModel;
        if (vm != null)
        {
            m_UIQueueManager.AddToQueue(new UIQueueItem<double, 
              string>(ClientFacade.Instance.GetContinentSize, 
              vm.SetSize, vm.Caption));
        }
    }

    private void GetCounrtyCurrency(IViewModel sender, object param)
    {
        ICountryViewModel vm = sender as ICountryViewModel;
        if (vm != null)
        {
            m_UIQueueManager.AddToQueue(
              new UIQueueItem<WorldDataLib.Model.CurrenciesEnum, 
              string>(ClientFacade.Instance.GetCountriesCurrency, 
              vm.SetCurrency, vm.Caption));
        }
    }

    private void GetCityTemprature(IViewModel sender, object param)
    {
        ICityViewModel vm = sender as ICityViewModel;
        if (vm != null)
        {
            m_UIQueueManager.AddToQueue(new UIQueueItem<double, 
              string>(ClientFacade.Instance.GetCityCurrentTemprature, 
                      vm.SetTemprature, vm.Caption));
        }            
    }

    #endregion
}

历史

  • 2009 年 7 月 29 日:初始发布。
© . All rights reserved.