The end goal is to have a WCF server that calls a callback function, and each subscribed WCF client displays a new instance of a form on the callback.
To accomplish this, I am using using a pub/sub pattern where the clients connect to the server via a named pipe and call Subscribe(), then receive a callback, EventReceived() when an event is read from the event log. In the implementation for the callback, the clients instantiate a new instance of a form and show it.
The problem is that the forms that are shown by the client freeze immediately. Any suggestions would be helpful. A simplified version of the code which exhibits the same behavior is below.
Client Code (Form1 is just a blank, default form with no additional code/decorators. Form2's only purpose in life is to record the SynchronizationContext.Current for the callbacks to use later.)
static class Program
{
public static SynchronizationContext SynchronizationContext;
[STAThread]
static void Main()
{
ClientContract callbacks = new ClientContract();
DuplexChannelFactory<IEventService> pipeFactory = new DuplexChannelFactory<IEventService>(
callbacks, new NetNamedPipeBinding(), new EndpointAddress("net.pipe://localhost/EventPipe"));
IEventService pipeProxy = pipeFactory.CreateChannel();
pipeProxy.Subscribe();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form2());
}
}
[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, UseSynchronizationContext = false)]
class ClientContract : IClientContract
{
public void EventReceived(int eventId)
{
Form1 newForm = new Form1();
newForm.Show();
}
}
Client Code - Form2 Constructor
public Form2()
{
InitializeComponent();
Program.SynchronizationContext = SynchronizationContext.Current;
}
Server Code
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;
}
Shared Code (implemented via a class library DLL file)
[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);
}
** edit **
As pointed out in the comments, the callback was received on a worker thread, but UI events need to happen on the main thread. I was able to get it to work, but it feels like a hack because I have a second, unused form, simply to get the SynchronizationContext for the callback to use.
Related
I have a COM object which is implemented in C#, and inherits from StandardOleMarshalObject to disable the NTA default behavior. For some reason, when I make a call to a server that makes a reentrant call to the client, the callback ends up on a different thread.
How do I ensure that all calls are made on the main thread?
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IComChat
{
void WriteLine(string text);
void Subscribe(IComChat callback);
}
public class ComChatServer : StandardOleMarshalObject, IComChat
{
private List<IComChat> Clients = new List<IComChat>();
public void WriteLine(string text)
{
foreach (var client in Clients)
{
// this makes a reentrant callback into the calling client
client.WriteLine(text);
}
}
public void Subscribe(IComChat client) => Clients.Add(client);
}
public class ComChatClient : StandardOleMarshalObject, IComChat
{
private IComChat Server;
private Thread MainThread;
public ComChatClient()
{
this.MainThread = Thread.CurrentThread;
this.Server = /* get server by some means */;
this.Server.Subscribe(this);
}
void IComChat.WriteLine(string text)
{
// this throws as the call ends up on a different thread
Contract.Assert(Thread.CurrentThread == MainThread);
Console.WriteLine(text);
}
void IComChat.Subscribe(IComChat callback) => throw new NotSupportedException();
public void WriteLine(string text) => Server.WriteLine(text);
}
public static class Program
{
public static void Main(string[] args)
{
var client = new ComChatClient();
Application.Run(new ChatWindow(client));
}
}
StandardOleMarshalObject only keeps things on the main thread if you create the object on a STA thread. Mark your entrypoint with [STAThread] to set your main thread as being single-threaded:
public static class Program
{
[STAThread]
public static void Main(string[] args)
{
var client = new ComChatClient();
Application.Run(new ChatWindow(client));
}
}
I made a WCF service which makes a callback to a WPF client. I just show the progress in a textbox in the WPF client. What I got first is cross thread operation not valid. Then I modified the client side code and implemented using methods such as Invoke() and BeginInvoke(). Now the client side code shows only the value 100%. Actually it should display the values from 0-100%. Any solutions?
The code at wcf service:
namespace ReportService
{
[ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Reentrant,InstanceContextMode=InstanceContextMode.Single)]
public class Report : IReportService
{
public void ProcessReport()
{
for (int i = 1; i <= 100; i++)
{
Thread.Sleep(1000);
OperationContext.Current.GetCallbackChannel<IReportCallback>().ReportProgress(i);
}
}
}
}
Code at client:
namespace Report
{
[CallbackBehavior(UseSynchronizationContext=false)]
public partial class Form1 : Form,ReportService.IReportServiceCallback
{
delegate void delSetTxt(int percentCompleted);
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
InstanceContext ic= new InstanceContext(this);
ReportService.ReportServiceClient client = new ReportService.ReportServiceClient(ic);
client.ProcessReport();
}
public void ReportProgress(int percentCompleted)
{
// this.Invoke(new Action(() => { this.textBox1.Text = percentCompleted.ToString(); }));
Thread t = new Thread(() => setTxt(percentCompleted));
t.Start();
}
public void setTxt(int percentCompleted)
{
if (this.InvokeRequired)
{
this.BeginInvoke(new delSetTxt(setTxt), new object[] { percentCompleted });
return;
}
this.textBox1.Text = percentCompleted.ToString() + " % complete";
}
}
}
When the call is made to the service, the GUI thread is stuck in the button_click method.
So the GUI thread must not be frozen.
There are (at least) two solutions that work, I tested them :
Put [OperationContract(IsOneWay = true)] both on the server and the callback operation
Put [OperationContract(IsOneWay = true)] on the callback operation and don't lock GUI thread with await/async:
private async void button1_Click(object sender, EventArgs e)
{
InstanceContext ic = new InstanceContext(this);
ReportServiceClient client = new ReportServiceClient(ic);
await client.ProcessReportAsync();
//client.ProcessReport();
}
Good luck
I am working on converting a console application into a windowed format and as someone who knows little about it but has had experience with a similar application already in window format in the past I figured it wouldn't be too hard.
So I created a form and added a textbox to it just to get the logging information to start with.
This console app used to run in a single thread, I have since added a second thread to allow the form to run side by side with the console for testing. (it runs fine in a single thread strangely now too).
This is the code I am using to write text to the form except that I am not getting ANYTHING at all on the form.
static Form1 f = new Form1();
delegate void SetTextCallback(string s);
private void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (f.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
f.textBox1.Invoke(d, new object[] { text });
}
else
{
f.textBox1.AppendText(text);
}
}
I can confirm that there is text entering the "text" variable but it is not getting to the form.
Any help would be appreciated.
This is the full file:
using System;
using System.Windows.Forms;
using Chraft.Properties;
using System.IO;
using Chraft.Plugins.Events.Args;
using Chraft.Plugins.Events;
namespace Chraft
{
public class Logger
{
private StreamWriter WriteLog;
private Server Server;
internal Logger(Server server, string file)
{
Server = server;
try
{
WriteLog = new StreamWriter(file, true);
WriteLog.AutoFlush = true;
}
catch
{
WriteLog = null;
}
}
~Logger()
{
try
{
WriteLog.Close();
}
catch
{
}
}
public void Log(LogLevel level, string format, params object[] arguments)
{
Log(level, string.Format(format, arguments));
}
public void Log(LogLevel level, string message)
{
//Event
LoggerEventArgs e = new LoggerEventArgs(this, level, message);
Server.PluginManager.CallEvent(Event.LOGGER_LOG, e);
if (e.EventCanceled) return;
level = e.LogLevel;
message = e.LogMessage;
//End Event
LogToConsole(level, message);
LogToForm(level, message);
LogToFile(level, message);
}
private void LogToConsole(LogLevel level, string message)
{
if ((int)level >= Settings.Default.LogConsoleLevel)
{
Console.WriteLine(Settings.Default.LogConsoleFormat, DateTime.Now, level.ToString().ToUpper(), message);
}
}
static Form1 f = new Form1();
delegate void SetTextCallback(string s);
private void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (f.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
f.textBox1.Invoke(d, new object[] { text });
}
else
{
f.textBox1.AppendText(text);
}
}
private void LogToForm(LogLevel level, string message)
{
if ((int)level >= Settings.Default.LogConsoleLevel)
{
SetText(DateTime.Now + level.ToString().ToUpper() + message);
}
}
private void LogToFile(LogLevel level, string message)
{
if ((int)level >= Settings.Default.LogFileLevel && WriteLog != null)
WriteLog.WriteLine(Settings.Default.LogFileFormat, DateTime.Now, level.ToString().ToUpper(), message);
}
public void Log(Exception ex)
{
//Event
LoggerEventArgs e = new LoggerEventArgs(this, LogLevel.Debug, ex.ToString(), ex);
Server.PluginManager.CallEvent(Event.LOGGER_LOG, e);
if (e.EventCanceled) return;
//End Event
Log(LogLevel.Debug, ex.ToString());
}
public enum LogLevel : int
{
Trivial = -1,
Debug = 0,
Info = 1,
Warning = 2,
Caution = 3,
Notice = 4,
Error = 5,
Fatal = 6
}
}
}
The problem is that you are creating two Form objects. One that is created in your Program.cs file:
Application.Run(new Form1());
And the one you created in your logger class
Form f = new Form1();
The one passed to Application.Run is the one that the user is interacting with. It has become visible and responds to user interaction because of the Application.Run call.
The one you created on your logger class just sits there in memory. Its TextBox is happily adding the text you ask it to, but that one isn't visible anywhere.
There are many ways to handle this situation. You could gain access to the correct Form object through Application.OpenForms, but a more appropriate way to handle it would be to add an event on the logger that the form can subscribe to and it can handle updating the TextBox in response to the event.
Updated
class LoggerLogEventArgs : EventArgs
{
public LoggerLogEventArgs(string message)
{
this.message = message;
}
private string message;
public string Message { get { return message; } }
}
class Logger
{
public event EventHandler<LoggerLogEventArgs> Logged;
protected virtual void OnLogged(LoggerLogEventArgs e)
{
EventHandler<LoggerLogEventArgs> handler = Logged;
if (handler != null)
handler(this, e);
}
// I would change this method name to LogToEvent
private void LogToForm(LogLevel level, string message)
{
if ((int)level >= Settings.Default.LogConsoleLevel)
{
OnLogged(new LoggerLogEventArgs(message));
}
}
}
class Form1 : Form
{
// Subscribe to the logger only when we are ready to display text
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
GetLog().Logged += new EventHandler<LoggerLogEventArgs>(logger_Logged);
}
// Unsubscribe from the logger before we are no longer ready to display text
protected override void OnHandleDestroyed(EventArgs e)
{
GetLog().Logged -= new EventHandler<LoggerLogEventArgs>(logger_Logged);
base.OnHandleDestroyed(e);
}
private void logger_Logged(object sender, LoggerLogEventArgs e)
{
if (InvokeRequired)
BeginInvoke(new EventHandler<LoggerLogEventArgs>(logger_Logged), e);
else
textBox1.AppendText(e.Message);
}
}
hello i try this it works ( I make a console application and I add a windows form)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Permissions;
using System.Windows.Forms;
namespace ConsoleApplication6
{
class Program
{
delegate void SetTextCallback(string s);
static Form1 f;
static void Main(string[] args)
{
f = new Form1();
f.Show();
SetText("test");
Console.ReadLine();
}
private static void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (f.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
f.textBox1.Invoke(d, new object[] { text });
}
else
{
f.textBox1.AppendText(text);
}
}
}
}
Here's my code so far. I have the main form and wcf objects that are created dynamically when a client connects. Right now all wcf objects subscribe to events being fired from the main form.
Yet I want the user to be able to pick a name from the main form's comboBox and fire an event only to the wcf object, that submitted this name.
What do you think would be the best way of doing this?
Thanks!
namespace server2
{
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
public event EventHandler sendEvnt;
private ServiceHost duplex;
private void Form2_Load(object sender, EventArgs e) /// once the form loads, create and open a new ServiceEndpoint.
{
duplex = new ServiceHost(typeof(ServerClass));
duplex.AddServiceEndpoint(typeof(IfaceClient2Server), new NetTcpBinding(), "net.tcp://localhost:9080/service");
duplex.Open();
this.Text = "SERVER *on-line*";
}
private void button1_Click(object sender, EventArgs e)
{
sendEvnt(this, new EventArgs());
// this send an event to all WCF objects
// what should I do for it to send an event ONLY to the wcf object, that's name is selected from the comboBox ?
}
}
class ServerClass : IfaceClient2Server
{
IfaceServer2Client callback;
public ServerClass()
{
callback = OperationContext.Current.GetCallbackChannel<IfaceServer2Client>();
}
public void StartConnection(string name)
{
var myForm = Application.OpenForms["Form2"] as Form2;
myForm.comboBox1.Items.Add(name);
myForm.comboBox1.SelectedItem = name; // adds a name to form's comboBox.
myForm.sendEvnt += new EventHandler(eventFromServer); // somehow need to incorporate the 'name' here.
callback.Message_Server2Client("Welcome, " + name );
}
void eventFromServer(object sender, EventArgs e)
{
var myForm = Application.OpenForms["Form2"] as Form2;
string msg = myForm.tb_send.Text;
if (msg == "") { msg = "empty message"; }
callback.Message_Server2Client(msg);
}
public void Message_Cleint2Server(string msg)
{
}
public void Message2Client(string msg)
{
}
}
[ServiceContract(Namespace = "server", CallbackContract = typeof(IfaceServer2Client), SessionMode = SessionMode.Required)]
public interface IfaceClient2Server ///// what comes from the client to the server.
{
[OperationContract(IsOneWay = true)]
void StartConnection(string clientName);
[OperationContract(IsOneWay = true)]
void Message_Cleint2Server(string msg);
}
public interface IfaceServer2Client ///// what goes from the sertver, to the client.
{
[OperationContract(IsOneWay = true)]
void AcceptConnection();
[OperationContract(IsOneWay = true)]
void RejectConnection();
[OperationContract(IsOneWay = true)]
void Message_Server2Client(string msg);
}
}
How are the WCF objects stored? I assume you are storing them in some sort of collection. If that is the case, try changing your collection to a Dictionary<string, WcfObjectType>. From there you can lookup the object in the Dictionary based on the string the user has entered.
Let's say I have an exposed interface as such:
interface IMyService
{
MyResult MyOperation();
}
This operation is synchronous and returns a value.
My implemented interface has to do the following:
Call an asynchronous method
Wait for event #1
Wait for event #2
This is due to a 3rd party COM object I am working with.
This code looks similar to the following
public MyResult MyOperation()
{
_myCOMObject.AsyncOperation();
//Here I need to wait for both events to fire before returning
}
private void MyEvent1()
{
//My Event 1 is fired in this handler
}
private void MyEvent2()
{
//My Event 2 is fired in this handler
}
My two events can happen in either order, it is quite random.
What is the proper threading mechanism I can use to synchronize this? I was using ManualResetEvent before I had to start waiting for the second event, and have not seen an easy way to use it for both events. These 2 events set variables that allow me to create the return value for MyOperation().
Any ideas on a good implementation for this? I have no control over the way the 3rd party object is implemented.
Two ManualResetEvents should do the trick for you. Just initialize them to false before you call the _myCOMObject.AsyncOperation(). Like this:
private ManualResetEvent event1;
private ManualResetEvent event2;
public MyResult MyOperation()
{
event1 = new ManualResetEvent(false);
event2 = new ManualResetEvent(false);
_myCOMObject.AsyncOperation();
WaitHandle.WaitAll(new WaitHandle[] { event1, event2 });
}
private void MyEvent1()
{
event1.Set();
}
private void MyEvent2()
{
event2.Set();
}
Edit
Thanks for the comments. I've changed the wait call to use WaitAll
My implementation example is as follows:
namespace ConsoleApplication1
{
class Program
{
private static WaitHandle[] waitHandles;
private static event EventHandler Evt1;
private static event EventHandler Evt2;
static void Main(string[] args)
{
waitHandles = new WaitHandle[]{
new ManualResetEvent(false),
new ManualResetEvent(false)
};
Evt1 += new EventHandler(Program_Evt1);
Evt2 += new EventHandler(Program_Evt2);
OnEvt1();
OnEvt2();
WaitHandle.WaitAll(waitHandles);
Console.WriteLine("Finished");
Console.ReadLine();
}
static void Program_Evt2(object sender, EventArgs e)
{
Thread.Sleep(2000);
((ManualResetEvent)waitHandles[0]).Set();
}
static void Program_Evt1(object sender, EventArgs e)
{
((ManualResetEvent)waitHandles[1]).Set();
}
static void OnEvt1()
{
if (Evt1 != null)
Evt1(null, EventArgs.Empty);
}
static void OnEvt2()
{
if (Evt2 != null)
Evt2(null, EventArgs.Empty);
}
}
}
I make it sleep for the purposes of this example and the WaitAll functionality
Cheers,
Andrew
P.S. another example would be using AsyncCallback, really quick and dirty example, but gives you more keys to open the door with :-) . Hope this helps!!
namespace ConsoleApplication1
{
class Program
{
private static WaitHandle[] waitHandles;
private static event EventHandler Evt1;
private static event EventHandler Evt2;
static void Main(string[] args)
{
waitHandles = new WaitHandle[]{
new ManualResetEvent(false),
new ManualResetEvent(false)
};
var callabck1 = new AsyncCallback(OnEvt1);
var callabck2 = new AsyncCallback(OnEvt2);
callabck1.Invoke(new ManualResetResult(null, (ManualResetEvent)waitHandles[0]));
callabck2.Invoke(new ManualResetResult(null, (ManualResetEvent)waitHandles[1]));
WaitHandle.WaitAll(waitHandles);
Console.WriteLine("Finished");
Console.ReadLine();
}
static void OnEvt1(IAsyncResult result)
{
Console.WriteLine("Setting1");
var handle = result.AsyncWaitHandle;
((ManualResetEvent)handle).Set();
}
static void OnEvt2(IAsyncResult result)
{
Thread.Sleep(2000);
Console.WriteLine("Setting2");
var handle = result.AsyncWaitHandle;
((ManualResetEvent)handle).Set();
}
}
public class ManualResetResult : IAsyncResult
{
private object _state;
private ManualResetEvent _handle;
public ManualResetResult(object state, ManualResetEvent handle)
{
_state = state;
_handle = handle;
}
#region IAsyncResult Members
public object AsyncState
{
get { return _state; }
}
public WaitHandle AsyncWaitHandle
{
get { return _handle; }
}
public bool CompletedSynchronously
{
get { throw new NotImplementedException(); }
}
public bool IsCompleted
{
get { throw new NotImplementedException(); }
}
#endregion
}
}
I am not sure I understood your question, but AutoResetEvent.WaitAll seems to solve your problem, if I got it right. It allows you to set more than one handler and it will only be released when all are set.
http://msdn.microsoft.com/en-us/library/z6w25xa6.aspx