C# 启动表单的WCF回调使该表单冻结
最终目标是拥有一个调用回调函数的WCF服务器,每个订阅的WCF客户机在回调上显示一个新的表单实例 为了实现这一点,我使用了一种pub/sub模式,其中客户端通过命名管道连接到服务器,并调用Subscribe(),然后在从事件日志读取事件时接收回调EventReceived()。在回调的实现中,客户端实例化一个表单的新实例并显示它 问题是客户端显示的表单会立即冻结。任何建议都会有帮助。下面是展示相同行为的代码的简化版本 客户端代码(Form1只是一个空白的默认表单,没有额外的代码/修饰符。Form2的唯一用途是记录SynchronizationContext.Current,以便回调以后使用。) 服务器代码C# 启动表单的WCF回调使该表单冻结,c#,multithreading,wcf,thread-safety,C#,Multithreading,Wcf,Thread Safety,最终目标是拥有一个调用回调函数的WCF服务器,每个订阅的WCF客户机在回调上显示一个新的表单实例 为了实现这一点,我使用了一种pub/sub模式,其中客户端通过命名管道连接到服务器,并调用Subscribe(),然后在从事件日志读取事件时接收回调EventReceived()。在回调的实现中,客户端实例化一个表单的新实例并显示它 问题是客户端显示的表单会立即冻结。任何建议都会有帮助。下面是展示相同行为的代码的简化版本 客户端代码(Form1只是一个空白的默认表单,没有额外的代码/修饰符。Form
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(typeof(EventService), new Uri[] { new Uri("net.pipe://localhost") }))
{
host.AddServiceEndpoint(typeof(IEventService), new NetNamedPipeBinding(), "EventPipe");
host.Open();
Console.WriteLine("Service started");
do
{
Console.WriteLine("Enter \"callback\" to trigger a callback, or enter to quit");
string input = Console.ReadLine();
if (input == "callback")
{
EventService.PublishEvent(3);
}
else if (input == "")
{
break;
}
} while (true);
host.Close();
}
}
}
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Single, InstanceContextMode = InstanceContextMode.PerCall)]
class EventService : IEventService
{
public static event NewEventHandler NewEventAvailableHandler;
public delegate void NewEventHandler(EventLogEventArgs e);
private NewEventHandler newEventHandler;
private IClientContract callback = null;
public static void PublishEvent(int eventId)
{
EventLogEventArgs e = new EventLogEventArgs();
e.eventId = eventId;
if (NewEventAvailableHandler != null)
{
NewEventAvailableHandler(e);
}
}
public void Subscribe()
{
callback = OperationContext.Current.GetCallbackChannel<IClientContract>();
newEventHandler = new NewEventHandler(MagazineService_NewIssueAvailableEvent);
NewEventAvailableHandler += newEventHandler;
}
public void Unsubscribe()
{
NewEventAvailableHandler -= newEventHandler;
}
public void MagazineService_NewIssueAvailableEvent(EventLogEventArgs e)
{
callback.EventReceived(e.eventId);
}
}
public class EventLogEventArgs : EventArgs
{
public int eventId;
}
**编辑**
正如注释中指出的,回调是在工作线程上接收的,但是UI事件需要在主线程上发生。我能够让它工作,但感觉像是一个黑客,因为我有第二个未使用的表单,只是为了获取SynchronizationContext供回调使用。如果在
Form1 newForm=new Form1()上设置断点代码>内部EventReceived(int-eventId)
,它是什么线程?主/GUI线程?它看起来像一个工作线程(请参阅)。主线程在Main()中的Application.Run()上阻塞,我没有做任何显式的操作来创建新线程(所有代码都在原始文章中)。好的。它是冻结的,因为您无法从工作线程创建/更新UI。让您的回调在应用程序的主窗口上执行BeginInvoke
或其他操作,以代表您显示表单。实际上,当您在工作线程中执行时,您无法更新在另一个线程中创建的现有UI元素。但您可以创建一个全新的表单——它将有自己的调度程序(当我们在WPF世界中时),这几乎不意味着它自己的Windows消息处理。更多信息:虽然这是可能的,但不建议在应用程序中出现这种情况,您最好将创建新窗口的请求发回主UI线程。谢谢你们的建议。我能够让它工作并更新帖子,但我这样做感觉像是一个黑客。我正在使用第二个表单,我甚至不需要它,只是为了在Application.Run()中获取SynchronizationContext.Current,只是为了稍后回调使用它来显示我关心的表单。肯定有更好的办法吗?
public Form2()
{
InitializeComponent();
Program.SynchronizationContext = SynchronizationContext.Current;
}
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(typeof(EventService), new Uri[] { new Uri("net.pipe://localhost") }))
{
host.AddServiceEndpoint(typeof(IEventService), new NetNamedPipeBinding(), "EventPipe");
host.Open();
Console.WriteLine("Service started");
do
{
Console.WriteLine("Enter \"callback\" to trigger a callback, or enter to quit");
string input = Console.ReadLine();
if (input == "callback")
{
EventService.PublishEvent(3);
}
else if (input == "")
{
break;
}
} while (true);
host.Close();
}
}
}
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Single, InstanceContextMode = InstanceContextMode.PerCall)]
class EventService : IEventService
{
public static event NewEventHandler NewEventAvailableHandler;
public delegate void NewEventHandler(EventLogEventArgs e);
private NewEventHandler newEventHandler;
private IClientContract callback = null;
public static void PublishEvent(int eventId)
{
EventLogEventArgs e = new EventLogEventArgs();
e.eventId = eventId;
if (NewEventAvailableHandler != null)
{
NewEventAvailableHandler(e);
}
}
public void Subscribe()
{
callback = OperationContext.Current.GetCallbackChannel<IClientContract>();
newEventHandler = new NewEventHandler(MagazineService_NewIssueAvailableEvent);
NewEventAvailableHandler += newEventHandler;
}
public void Unsubscribe()
{
NewEventAvailableHandler -= newEventHandler;
}
public void MagazineService_NewIssueAvailableEvent(EventLogEventArgs e)
{
callback.EventReceived(e.eventId);
}
}
public class EventLogEventArgs : EventArgs
{
public int eventId;
}
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IClientContract))]
public interface IEventService
{
[OperationContract(IsOneWay = true, IsInitiating = true)]
void Subscribe();
[OperationContract(IsOneWay = true, IsInitiating = true)]
void Unsubscribe();
}
public interface IClientContract
{
[OperationContract(IsOneWay = true)]
void EventReceived(int eventId);
}