使用 DispatcherObject 的具有多个客户端的服务架构





5.00/5 (2投票s)
展示一种服务架构,该架构使用DispatcherObject在各自的线程上处理多个客户端。
引言
假设您有一个服务(不是WCF或Windows服务),它需要向其客户端更新服务中的更改,并且您不想使用GUI线程来避免阻塞用户界面,那么您该怎么做呢?
您可以创建一个线程并更新客户端,但是当出现问题时,调试起来可能会很困难,这就是为什么这种架构更易于调试的原因,因为每个客户端都将拥有自己的线程,该线程以客户端的ID和创建时的时间戳命名。(这部分实际上并非必要,但在本例中用于演示在向服务注册和注销时,它确实是一个新的线程。)
我提出的架构受到了我一位同事的启发。
在此处下载代码 此处。
架构介绍
首先,每个客户端都需要实现一个接口,在本例中称为IClient
。
public interface IClient : IClientCallbackEvents
{
/// <summary>
/// Get the name of the client
/// </summary>
string Name { get; }
/// <summary>
/// Get the client Id
///
/// For internal use
/// </summary>
string Id { get; }
}
public interface IClientCallbackEvents
{
/// <summary>
/// Fired when you are registered
/// </summary>
void OnRegistred();
/// <summary>
/// Fired when you are unregistered
/// </summary>
void OnUnRegistred();
/// <summary>
/// Fired when the time changes
/// </summary>
/// <param name="time"></param>
void OnTimeChanged(DateTime time);
}
如您所见,IClient
接口继承了IClientCallbackEvents
接口,在本例中,该接口具有OnTimeChanged
通知事件,当时间更改时,将在客户端自己的线程上触发。在本例中,它将每秒为每个客户端调用一次。当客户端注册和注销时,将调用另外两个事件。
让我们看看ClientDipatcher
类的实现方式。它是负责使用DispatcherObject
类为每个客户端提供单独线程的类。
class ClientDispatcher : DispatcherObject, IClientCallbackEvents, IDisposable
{
#region Fields
private IClient m_Client;
private bool m_Disposed = false;
#endregion
#region Constructors
public ClientDispatcher(IClient client)
{
m_Client = client;
}
#endregion
#region Properties
#endregion
#region Public Methods
public void Dispose()
{
if (m_Disposed)
return;
if (!Dispatcher.HasShutdownStarted)
Dispatcher.BeginInvokeShutdown(DispatcherPriority.Normal);
m_Disposed = true;
}
#endregion
#region Helper Methods
#endregion
#region Event Handling
public void OnRegistred()
{
if (!CheckAccess())
{
Action action = () => OnRegistred();
Dispatcher.BeginInvoke(action);
}
else
m_Client.OnRegistred();
}
public void OnTimeChanged(DateTime time)
{
if (!CheckAccess())
{
Action<DateTime> action = (dt) => OnTimeChanged(dt);
Dispatcher.BeginInvoke(action, time);
}
else
m_Client.OnTimeChanged(time);
}
public void OnUnRegistred()
{
if (!CheckAccess())
{
Action action = () => OnUnRegistred();
Dispatcher.BeginInvoke(action);
}
else
m_Client.OnUnRegistred();
}
#endregion
}
ClientDispatcher
在构造函数中将客户端作为参数,并具有在正确的Dispatcher
上调用客户端的方法。CheckAccess
方法确保我们在正确的线程上调用通知。
为了管理所有客户端,ClientManager
类存在。它负责注册、注销以及保存每个客户端及其ClientDispatcher
的实例。它的实现如下所示:
public static class ClientManager
{
#region Fields
private static Dictionary<IClient,
ClientDispatcher> m_ClientsDict = new Dictionary<IClient, ClientDispatcher>();
private static System.Timers.Timer m_Timer = new System.Timers.Timer(1000); //Tick each second
#endregion
#region Constructors
static ClientManager()
{
Application.Current.Exit += OnApplication_Exit;
m_Timer.Elapsed += OnTimer_Elapsed;
m_Timer.Start();
}
#endregion
#region Properties
#endregion
#region Public Methods
public static void Register(IClient client)
{
if (client == null)
return;
if (m_ClientsDict.ContainsKey(client))
return;
Action action = () =>
{
ClientDispatcher dispatcher = new ClientDispatcher(client);
m_ClientsDict.Add(client, dispatcher);
Thread.CurrentThread.Name = $"{client.Id} - {DateTime.Now.ToString("HH:mm:ss")}";
//Notify the client
Task.Factory.StartNew(() => dispatcher.OnRegistred());
//Keep the dispatcher alive.
System.Windows.Threading.Dispatcher.Run();
};
Thread thread = new Thread(new ThreadStart(action));
thread.Start();
}
public static void UnRegister(IClient client)
{
if (client == null)
return;
if (!m_ClientsDict.ContainsKey(client))
return;
ClientDispatcher dispatcher = m_ClientsDict[client];
m_ClientsDict.Remove(client);
//Notify the client
dispatcher.OnUnRegistred();
dispatcher.Dispose();
}
#endregion
#region Helper Methods
private static void NotifyOnTimeChanged(DateTime time)
{
foreach (ClientDispatcher dispatcher in m_ClientsDict.Values.ToList())
dispatcher.OnTimeChanged(time);
}
#endregion
#region Event Handling
private static void OnTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
//stop the timer while notifying clients
m_Timer.Stop();
//Notify the clients on their own thread
NotifyOnTimeChanged(DateTime.Now);
m_Timer.Start();
}
private static void OnApplication_Exit(object sender, ExitEventArgs e)
{
foreach (IClient client in m_ClientsDict.Keys.ToList())
UnRegister(client);
}
#endregion
}
这里最有趣的部分是,当客户端注册时,将创建一个新Thread
,并使用client.Id
和timestamp
命名。然后,通过调用以下内容使线程调度程序保持活动状态:
System.Windows.Threading.Dispatcher.Run();
这就是为什么ClientDispatcher
上的Dispose()
很重要,因为它会在调用注销时关闭Dispatcher
。
其余的只是GUI代码,用于演示每个客户端如何在自己的线程上真正收到通知。用户界面如下所示,每个客户端都有两个按钮。一个注册按钮和一个注销按钮,它们使用delegatecommand
进行点击。然后,它将根据应该执行的操作调用ClientManager
,而ClientDispatcher
将根据请求触发事件。一旦客户端注册,它们将每秒钟使用OnTimeChanged
事件被调用。请注意,这只是为了演示架构。在实际应用程序中,您将有更多针对您需求的事件。
关于string
的格式化,我使用的是新的语法。
CurrentTime = $"{time.ToString("HH:mm:ss")}
ThreadName = $"Thread: '{Thread.CurrentThread.Name}'";
查看源代码,希望您喜欢这种“新”模式。