WPF 中的 I/O 操作






4.50/5 (5投票s)
本文介绍了一种在 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 日:初始发布。