I'm writing a Windows Phone 7 application which utilises Push Notifications and have a class which is responsible for managing interactions between the MS Notification Servers and my service in the cloud. However when I'm attempting to open the channel on my device HttpNotificationChannel is throwing an InvalidOperationException with the message "Failed to open channel". According to MSDN I should try opening the channel again.
My snippet of code to open the push notification is following the standard pattern of;
public class HttpNotification {
private const string kChannelName = "MyApp.PushNotification";
private HttpNotificationChannel _Channel;
public void Register() {
try {
_Channel = HttpNotificationChannel.Find(kChannelName);
if (_Channel == null) {
_Channel = new HttpNotificationChannel(kChannelName);
InstallEventHandlers();
// This line throws
_Channel.Open();
} else {
InstallEventHandlers();
};
} catch (InvalidOperationException ex) {
MessageBox.Show(string.Format("Failed to initialise Push Notifications - {0}", ex.Message));
};
}
}
I'm not sure exactly what MSDN means by "try opening the channel again". I've wrapped the call to Open() in a try/catch and snoozing 5 seconds between attempts but it doesn't succeed. I've also tried the same approach around the entire method (ie. Do the call to HttpNotificationChannel.Find() each time it throws) to no avail.
I know this is a tad bit vague - but was wondering if anyone has any suggestions on handling this? This same code works flawlessly in the emulator, but fails every time on my actual device, even after an un-install and re-install of my application. Given that this is my actual phone, I'm a little reticent to do a hardware reset in the hope that it solves this issue, and don't feel comfortable releasing the application to the marketplace with this issue haunting me.
Update: An additional point, I'm using an unauthenticated channel, so there's no certificate installed for my cloud-based service.
Update #2: Further, I just tried deploying the Microsoft Phone Push Recipe to my device and it's also throwing the same exception.
So from your comment I understand that it does work on your emulator but not on your phone right?
Did you by any chance use the channel name in another/prior application?
The thing is that the emulator reset back to it's default state everyime it closes, your phone does not. A particular channel name can only be used by a single application. So if the channel name was used by another application on the same phone before it is still registered to that app and you can't access it from your app.
Conversely an app can also regsiter no more than one channel so if there is allready one by another name associated with it you cannot register a new one until you unregister the old one and reboot your device. Also there is no way to request which channel is associated with your app.
Ultimately when I got stuck in this loop I changed the name of the channel and my applications ProductID registered in the WMAppManifest.xml and it worked again form me
<App xmlns="" ProductID="{d57ef66e-f46c-4b48-ac47-22b1e924184b}"
Update
My computer crashed this weekend, thank god for WHS and backups.
Anyway below is my sourcecode. I notice a two differences.
First off I created a method called RepeatAttemptExecuteMethod() to which I pass the entire executing code as a delegate. The 10 floating somewhere at the end is the amount of times it has to retry. If you only retried the .Open method every 5 seconds the difference might be in that I also call the Find and New methods again...
Another difference I see is that my code assumes that the _appChannel.ChannelUri can be null. In which case it waits for the channel to raise an event and then does the work asociated with a actual channel being there. But since your samplecode doesn't do any of that sort of work I doubt it will be what you are looking for
protected override void Load(PhoneApplicationPage parent)
{
Verkeer.Helper.ExternalResources.RepeatAttemptExecuteMethod(() =>
{
_appChannel = HttpNotificationChannel.Find(CHANNELNAME);
if (_appChannel == null)
{
_appChannel = new HttpNotificationChannel(CHANNELNAME);
SetUpDelegates();
}
else
{
SetUpDelegates();
//if (_appChannel.ChannelUri != null) this.NotificationChannel = _appChannel.ChannelUri;
}
if (_appChannel.ChannelUri != null) this.NotificationChannel = _appChannel.ChannelUri;
else
{
try
{
_appChannel.Open();
}
catch { }
}
BindToShellTile();
App.ViewModel.TrafficInfo.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(TrafficInfo_PropertyChanged);
if (App.ViewModel.TrafficInfo.TrafficImage != null && this.NotificationChannel != null)
{
CreateTiles();
}
},10);
}
private void BindToShellTile()
{
if (!_appChannel.IsShellTileBound && App.ViewModel.PanItemSettings.AutomaticallyUpdateTile)
{
Collection<Uri> ListOfAllowedDomains = new Collection<Uri> { new Uri("http://m.anwb.nl/") };
_appChannel.BindToShellTile(ListOfAllowedDomains);
}
}
void TrafficInfo_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "TrafficImage")
{
if (App.ViewModel.PanItemSettings.AutomaticallyUpdateTile && this.NotificationChannel != null)
{
CreateTiles();
}
}
}
#slaad .. here are few things that I would check, unless you have already tried these:
Your actual device does have data connectivity, right? doh :)
How are you storing an existing Channel in Isolated Storage? Make sure your Find() is working & that you are not trying to recreate a channel that exists leading to exception.
Check if your Channel creation has issues with domain name or certs. Try this link
Check every step of your process against this
Sorry, not being of much more help than this.
Related
Problem:
I've read and tried a few different iterations of the "Build Presence..." documentation found here and elsewhere: https://firebase.google.com/docs/firestore/solutions/presence, but have been unable to have a proper disconnect action taken upon a user losing their internet connection. There is a similar post, but the question is not answered using what I think to be the true intent of OnDisconnect (but I could certainly be wrong in my understanding).
Goal:
Upon a user losing their connection to the internet, or other unanticipated DC, the "online" value for that user should be set to "F".
Question:
Does anyone have suggestions on how to properly code a presence check system in Unity with Firebase, or can point out what I've done incorrectly in the code below?
Background Info:
My Firebase Realtime Database currently has no limitations on reads/writes.
Here is sample code that I've tried. It works properly upon forced application closed (i.e. end process action), but does not work when a user DCs from the internet. I've let it sit for several minutes after DCing to ensure it was not just a server delay issue.
private void Start() //removed some unrelated lines of code from this method
{
PresenceCheck();
}
public void PresenceCheck() //listen for change
{
var connectCheck = FirebaseDatabase.DefaultInstance.GetReference(".info/connected");
connectCheck.ValueChanged += CheckForDisconnect;
}
private void CheckForDisconnect(object sender, ValueChangedEventArgs args)
{
var checkBool = args.Snapshot;
if (checkBool == null) //if null, do nothing for now
{
return;
}
else if ((bool)checkBool.GetValue(true)) //if connected, set online to "T" and on disconnect set online value to "F"
{
FirebaseDatabase.DefaultInstance.GetReference("users").Child(userID).Child("online").OnDisconnect().SetValue("F");
FirebaseDatabase.DefaultInstance.GetReference("users").Child(userID).Child("online").SetValueAsync("T");
}
}
What I want to do:
- synchronously (or even asynchronously) load settings from USB drive before first page loads
What I did:
- in OnLaunched method for App.xaml.cs I invoked this static function:
public static async void LoadSettings(string folderName = "Config", string fileName = "Settings.xml")
{
try
{
StorageFile configFile = null;
// scan through all devices
foreach (var device in await KnownFolders.RemovableDevices.GetFoldersAsync().AsTask().ConfigureAwait(false))
{
// folder that should have configuration
var configFolder = await device.GetFolderAsync(folderName).AsTask().ConfigureAwait(false);
if (configFile != null && configFolder != null && await configFolder.GetFileAsync(fileName).AsTask().ConfigureAwait(false) != null)
{
throw new Exception("More than one configuration file detected. First found configuration file will be used.");
}
else
configFile = await configFolder.GetFileAsync(fileName).AsTask().ConfigureAwait(false);
}
if (configFile == null)
throw new Exception("Configuration file was not found, please insert device with proper configuration path.");
string settingString = await FileIO.ReadTextAsync(configFile).AsTask().ConfigureAwait(false);
XmlSerializer serializer = new XmlSerializer(typeof(Settings));
using (TextReader reader = new StringReader(settingString))
{
AppSettings = (Settings)serializer.Deserialize(reader); // store settings in some static variable
}
}
catch (Exception e)
{
//return await Task.FromResult<string>(e.Message);
}
//return await Task.FromResult<string>(null);
}
As you can see right now it's async void method, so I don't even want to synchronize it in any way with UI thread. It should just fire and do something. With ConfigureAwait(false) I want to be sure that it will never try to return to context. These returns at the end are remnants of other things I tried (I wanted to do this better way, this is the most primitive solution and it still doesn't work).
Anyway, because that's where the fun begins: everything works well when I debug application on local machine with Win 10. And I get deadlocked thread on Win 10 IOT installed on Raspberry Pi 3 (I installed it from the scratch today, last version).
But deadlock is not the weirdest thing. Weirdest thing is when it appears.
Like I said, invocation of this method looks like that:
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
Configuration.Settings.LoadSettings();
After that everything in this method goes normally, so I navigate to my first page somewhere below:
if (e.PrelaunchActivated == false)
{
if (rootFrame.Content == null)
{
rootFrame.Navigate(typeof(LogScreen), e.Arguments);
}
Window.Current.Activate();
}
Everything still works. User needs to write his code, I check if this code is available in settings and after that user can press "OK" to move to next page. Somewhere in LogScreenViewModel this method is responsible for that:
private void GoForward(bool isValid)
{
try
{
_navigationService.NavigateTo("MainPage"); // it's SimpleIoc navigation from MVVMLight
}
catch (Exception e)
{
Debug.WriteLine($"ERROR: {e.Message}");
}
}
And deadlock happens when _navigationService.NavigateTo("MainPage") is reached. Basically right now UI thread freezes. If I wait for long enough I will see catched exception in Output saying that messenger seemed occupied (I can't show the screen because I don't have access to that Raspberry right now) and after some timeout this thread was killed (like 30 seconds or something) - after that UI thread unlocks and application proceeds to MainPage. It doesn't happen on PC - MainPage appears immediately, no exceptions, no deadlocks.
I tried waiting on first page for like 1 minute to check if some deadlock exception would fire on it's own - but it doesn't. It will fire ONLY after I try to proceed to next page.
What else I tried instead of this fire-and-forget approach:
Making OnLaunched async and await LoadSettings returning Task - same thing happens in the same place, and no problem on PC.
Using:
Window.Current.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () => await Configuration.Settings.LoadSettings(); ).AsTask().Wait(); If I remember correctly it deadlocked immediately on Wait(), even with ConfigureAwait(false) everywhere, but it also happened on PC.
Allowing LogScreen to load, make it's OnNavigatedTo method async and await LoadSettings - same deadlock in same place
Allowing LogScreen to load and use Dispatcher from there like in point 2. It deadlocked the same way after reaching Wait(), on PC too.
Trying to force LoadSettings to be fully synchronous by replacing every await with AsTask().GetAwaiter().GetResults(). It worked well on PC... and of course deadlock on Raspberry.
What am I missing? What else can I try? Because to be honest right now it looks to me that Win 10 IOT .NET runtime is bugged or something.
I think I resolved the issue. This code was generally speaking not mine and after some digging I noticed that someone before me tried to list some other external devices while navigating to MainPage. It was not really async-safe code (someone probably wasn't aware of synchronization context) and it worked on Win 10 only because on desktop it was looking for COM0 device and I only have COM2, so method causing trouble was not even invoked at all.
I still have no idea how related it was to my configuration (because it somehow was working without it), but after I fixed issues with this old not-async-safe code it started to behave as expected.
I currently have a single application that needs to be started from a windows service that i am coding in .net 3.5. This application is currently running as the user who ran the service, in my case the SYSTEM user. If running as the SYSTEM user it does not show the application to the users desktop. Thoughts? advice?
//constructor
private Process ETCHNotify = new Process();
//StartService()
ETCHNotify.StartInfo.FileName = baseDir + "\\EtchNotify.exe";
ETCHNotify.StartInfo.UseShellExecute = false;
//BackgroundWorkerThread_DoWork()
if (!systemData.GetUserName().Equals(""))
{
// start ETCHNotify
try {
ETCHNotify.Start();
}
catch (Exception ex)
{
systemData.Run("ERR: Notify can't start: " + ex.Message);
}
}
I only execute the try/catch if the function i have written GetUserName() (which determines the username of the user running explorer.exe) is not null
again to reiterate: desired functionality is that this starts ETCHNotify in a state that allows it to interact with the currently logged in user as determined by GetUserName()
Collage of some post found around (this and this)
Note that as of Windows Vista, services are strictly forbidden from interacting directly with a user:
Important: Services cannot directly interact with a user as of Windows
Vista. Therefore, the techniques mentioned in the section titled Using
an Interactive Service should not be used in new code.
This "feature" is broken, and conventional wisdom dictates that you shouldn't have been relying on it anyway. Services are not meant to provide a UI or allow any type of direct user interaction. Microsoft has been cautioning that this feature be avoided since the early days of Windows NT because of the possible security risks.
There are some possible workarounds, however, if you absolutely must have this functionality. But I strongly urge you to consider its necessity carefully and explore alternative designs for your service.
Use WTSEnumerateSessions to find the right desktop, then CreateProcessAsUser to start the application on that desktop (you pass it the handle of the desktop as part of the STARTUPINFO structure) is correct.
However, I would strongly recommend against doing this. In some environments, such as Terminal Server hosts with many active users, determining which desktop is the 'active' one isn't easy, and may not even be possible.
A more conventional approach would be to put a shortcut to a small client app for your service in the global startup group. This app will then launch along with every user session, and can be used start other apps (if so desired) without any juggling of user credentials, sessions and/or desktops.
Ultimately in order to solve this i took the advice of #marco and the posts he mentioned. I have created the service to be entirely independent of the tray application that interacts with the user. I did however install the Tray application via registry 'start up' methods with the service. The Service installer will now install the application which interacts with the user as well... This was the safest and most complete method.
thanks for your help everyone.
I wasn't going to answer this since you already answered it, (and it's oh, what? going on 2.5 years OLD now!?) But there are ALWAYS those people who are searching for this same topic, and reading the answers...
In order to get my service to Interact with the Desktop, no matter WHAT desktop, nor, how MANY desktops, nor if the service was even running on the SAME COMPUTER as the desktop app!! None of that matters with what I got here... I won't bore you with the details, I'll just give you the meat and potatoes, and you and let me know if you want to see more...
Ok. First thing I did was create an Advertisement Service. This is a thread that the service runs, opens up a UDP socket to listen for broadcasts on the network. Then, using the same piece of code, I shared it with the client app, but it calls up Advertise.CLIENT, rather than Advertise.SERVER... The CLIENT opens the port I expect the service to be on, and broadcasts a message, "Hello... Is there anybody out there??", asking if they're there ANY servers listening, and if so, reply back to THIS IP address with your computer name, IP Address and port # where I can find the .NET remoting Services..." Then it waits a small amount of time-out time, gathers up the responses it gets, and if it's more than one, it presents the user with a dialog box and a list of services that responded... The Client then selects one, or, if only ONE responded, it will call Connect((TServerResponse) res); on that, to get connected up. At this point, the server is using Remoting Services with the WellKnownClientType, and WellKnownServerType to put itself out there...
I don't think you are too interested in my "Auto-Service locater", because a lot of people frown on UDP, even more so when your app start broadcasting on large networks. So, I'm assuming you'd be more interested in my RemotingHelper, that gets the client connected up to the server. It looks like this:
public static Object GetObject(Type type)
{
try {
if(_wellKnownTypes == null) {
InitTypeCache();
}
WellKnownClientTypeEntry entr = (WellKnownClientTypeEntry)_wellKnownTypes[type];
if(entr == null) {
throw new RemotingException("Type not found!");
}
return System.Activator.GetObject(entr.ObjectType, entr.ObjectUrl);
} catch(System.Net.Sockets.SocketException sex) {
DebugHelper.Debug.OutputDebugString("SocketException occured in RemotingHelper::GetObject(). Error: {0}.", sex.Message);
Disconnect();
if(Connect()) {
return GetObject(type);
}
}
return null;
}
private static void InitTypeCache()
{
if(m_AdvertiseServer == null) {
throw new RemotingException("AdvertisementServer cannot be null when connecting to a server.");
}
_wellKnownTypes = new Dictionary<Type, WellKnownClientTypeEntry>();
Dictionary<string, object> channelProperties = new Dictionary<string, object>();
channelProperties["port"] = 0;
channelProperties["name"] = m_AdvertiseServer.ChannelName;
Dictionary<string, object> binFormatterProperties = new Dictionary<string, object>();
binFormatterProperties["typeFilterLevel"] = "Full";
if(Environment.UserInteractive) {
BinaryServerFormatterSinkProvider binFormatterProvider = new BinaryServerFormatterSinkProvider(binFormatterProperties, null);
_serverChannel = new TcpServerChannel(channelProperties, binFormatterProvider);
// LEF: Only if we are coming form OUTSIDE the SERVICE do we want to register the channel, since the SERVICE already has this
// channel registered in this AppDomain.
ChannelServices.RegisterChannel(_serverChannel, false);
}
System.Diagnostics.Debug.Write(string.Format("Registering: {0}...\n", typeof(IPawnStatServiceStatus)));
RegisterType(typeof(IPawnStatServiceStatus),m_AdvertiseServer.RunningStatusURL.ToString());
System.Diagnostics.Debug.Write(string.Format("Registering: {0}...\n", typeof(IPawnStatService)));
RegisterType(typeof(IPawnStatService), m_AdvertiseServer.RunningServerURL.ToString());
System.Diagnostics.Debug.Write(string.Format("Registering: {0}...\n", typeof(IServiceConfiguration)));
RegisterType(typeof(IServiceConfiguration), m_AdvertiseServer.RunningConfigURL.ToString());
}
[SecurityPermission(SecurityAction.Demand, Flags=SecurityPermissionFlag.RemotingConfiguration, RemotingConfiguration=true)]
public static void RegisterType(Type type, string serviceUrl)
{
WellKnownClientTypeEntry clientType = new WellKnownClientTypeEntry(type, serviceUrl);
if(clientType != RemotingConfiguration.IsWellKnownClientType(type)) {
RemotingConfiguration.RegisterWellKnownClientType(clientType);
}
_wellKnownTypes[type] = clientType;
}
public static bool Connect()
{
// Init the Advertisement Service, and Locate any listening services out there...
m_AdvertiseServer.InitClient();
if(m_AdvertiseServer.LocateServices(iTimeout)) {
if(!Connected) {
bConnected = true;
}
} else {
bConnected = false;
}
return Connected;
}
public static void Disconnect()
{
if(_wellKnownTypes != null) {
_wellKnownTypes.Clear();
}
_wellKnownTypes = null;
if(_serverChannel != null) {
if(Environment.UserInteractive) {
// LEF: Don't unregister the channel, because we are running from the service, and we don't want to unregister the channel...
ChannelServices.UnregisterChannel(_serverChannel);
// LEF: If we are coming from the SERVICE, we do *NOT* want to unregister the channel, since it is already registered!
_serverChannel = null;
}
}
bConnected = false;
}
}
So, THAT is meat of my remoting code, and allowed me to write a client that didn't have to be aware of where the services was installed, or how many services were running on the network. This allowed me to communicate with it over the network, or on the local machine. And it wasn't a problem to have two or more people running the app, however, yours might. Now, I have some complicated callback code in mine, where I register events to go across the remoting channel, so I have to have code that checks to see if the client is even still connected before I send the notification to the client that something happened. Plus, if you are running for more than one user, you might not want to use Singleton objects. It was fine for me, because the server OWNS the objects, and they are whatever the server SAYS they are. So, my STATS object, for example, is a Singleton. No reason to create an instance of it for EVERY connection, when everyone is going to see the same data, right?
I can provide more chunks of code if necessary. This is, of course, one TINY bit of the overall picture of what makes this work... Not to mention the subscription providers, and all that.
For the sake of completeness, I'm including the code chunk to keep your service connected for the life of the process.
public override object InitializeLifetimeService()
{
ILease lease = (ILease)base.InitializeLifetimeService();
if(lease.CurrentState == LeaseState.Initial) {
lease.InitialLeaseTime = TimeSpan.FromHours(24);
lease.SponsorshipTimeout = TimeSpan.FromSeconds(30);
lease.RenewOnCallTime = TimeSpan.FromHours(1);
}
return lease;
}
#region ISponsor Members
[SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.Infrastructure)]
public TimeSpan Renewal(ILease lease)
{
return TimeSpan.FromHours(12);
}
#endregion
If you include the ISponsor interface as part of your server object, you can implement the above code.
Hope SOME of this is useful.
When you register your service, you can tell it to allow interactions with the desktop. You can read this oldie link http://www.codeproject.com/KB/install/cswindowsservicedesktop.aspx
Also, don't forget that you can have multiple users logged in at the same time.
Apparently on Windows Vista and newer interacting with the desktop has been made more difficult. Read this for a potential solution: http://www.codeproject.com/KB/cs/ServiceDesktopInteraction.aspx
I've got a project called DotRas on CodePlex that exposes a component called RasConnectionWatcher which uses the RasConnectionNotification Win32 API to receive notifications when connections on a machine change. One of my users recently brought to my attention that if the machine comes out of sleep mode, and attempts to redial the connection, the connection goes into a loop indicating the connection is already being dialed even though it isn't. This loop will not end until the application is restarted, even if done through a synchronous call which all values on the structs are unique for that specific call, and none of it is retained once the call completes.
I've done as much as I can to fix the problem, but I fear the problem is something I've done with the RasConnectionNotification API and using ThreadPool.RegisterWaitForSingleObject which might be blocking something else in Windows.
The below method is used to register 1 of the 4 change types the API supports, and the handle to associate with it to monitor. During runtime, the below method would be called 4 times during initialization to register all 4 change types.
private void Register(NativeMethods.RASCN changeType, RasHandle handle)
{
AutoResetEvent waitObject = new AutoResetEvent(false);
int ret = SafeNativeMethods.Instance.RegisterConnectionNotification(handle, waitObject.SafeWaitHandle, changeType);
if (ret == NativeMethods.SUCCESS)
{
RasConnectionWatcherStateObject stateObject = new RasConnectionWatcherStateObject(changeType);
stateObject.WaitObject = waitObject;
stateObject.WaitHandle = ThreadPool.RegisterWaitForSingleObject(waitObject, new WaitOrTimerCallback(this.ConnectionStateChanged), stateObject, Timeout.Infinite, false);
this._stateObjects.Add(stateObject);
}
}
The event passed into the API gets signaled when Windows detects a change in the connections on the machine. The callback used just takes the change type registered from the state object and then processes it to determine exactly what changed.
private void ConnectionStateChanged(object obj, bool timedOut)
{
lock (this.lockObject)
{
if (this.EnableRaisingEvents)
{
try
{
// Retrieve the active connections to compare against the last state that was checked.
ReadOnlyCollection<RasConnection> connections = RasConnection.GetActiveConnections();
RasConnection connection = null;
switch (((RasConnectionWatcherStateObject)obj).ChangeType)
{
case NativeMethods.RASCN.Disconnection:
connection = FindEntry(this._lastState, connections);
if (connection != null)
{
this.OnDisconnected(new RasConnectionEventArgs(connection));
}
if (this.Handle != null)
{
// The handle that was being monitored has been disconnected.
this.Handle = null;
}
this._lastState = connections;
break;
}
}
catch (Exception ex)
{
this.OnError(new System.IO.ErrorEventArgs(ex));
}
}
}
}
}
Everything works perfectly, other than when the machine comes out of sleep. Now the strange thing is when this happens, if a MessageBox is displayed (even for 1 ms and closed by using SendMessage) it will work. I can only imagine something I've done is blocking something else in Windows so that it can't continue processing while the event is being processed by the component.
I've stripped down a lot of the code here, the full source can be found at:
http://dotras.codeplex.com/SourceControl/changeset/view/68525#1344960
I've come for help from people much smarter than myself, I'm outside of my comfort zone trying to fix this problem, any assistance would be greatly appreciated!
Thanks! - Jeff
After a lot of effort, I tracked down the problem. Thankfully it wasn't a blocking issue in Windows.
For those curious, basically once the machine came out of sleep the developer was attempting to immediately dial a connection (via the Disconnected event). Since the network interfaces hadn't finished initializing, an error was returned and the connection handle was not being closed. Any attempts to close the connection would throw an error indicating the connection was already closed, even though it wasn't. Since the handle was left open, any subsequent attempts to dial the connection would cause an actual error.
I just had to make an adjustment in the HangUp code to hide the error thrown when a connection is closed that has already been closed.
I am using a netNamedPipeBinding to perform inter-process WCF communication from a windows app to a windows service.
Now my app is running well in all other accounts (after fighting off my fair share of WCF exceptions as anybody who has worked with WCF would know..) but this error is one that is proving to be quite resilient.
To paint a picture of my scenario: my windows service could be queued to do some work at any given time through a button pressed in the windows app and it then talks over the netNamedPipeBinding which is a binding that supports callbacks (two-way communication) if you are not familiar and initiates a request to perform this work, (in this case a file upload procedure) it also throws the callbacks (events) every few seconds ranging from file progress to transfer speed etc. etc. back to the windows app, so there is some fairly tight client-server integration; this is how I receive my progress of what's running in my windows service back into my windows app.
Now, all is great, the WCF gods are relatively happy with me right now apart from one nasty exception which I receive every time I shutdown the app prematurely (which is a perfectly valid scenario). Whilst a transfer is in progress, and callbacks are firing pretty heavily, I receive this error:
System.ServiceModel.ProtocolException:
The channel received an unexpected input message with Action
'http://tempuri.org/ITransferServiceContract/TransferSpeedChangedCallback'
while closing. You should only close your channel when you are not expecting
any more input messages.
Now I understand that error, but unfortunately I cannot guarantee to close my channel after never receiving any more input messsages, as the user may shutdown the app at any time therefore the work will still be continuing in the background of the windows service (kind of like how a virus scanner operates). The user should be able to start and close the win management tool app as much as they like with no interference.
Now the error, I receive immediately after performing my Unsubscribe() call which is the second last call before terminating the app and what I believe is the preferred way to disconnect a WCF client. All the unsubscribe does before closing the connection is simply removes the client id from an array which was stored locally on the win service wcf service (as this is an instance SHARED by both the win service and windows app as the win service can perform work at scheduled events by itself) and after the client id array removal I perform, what I hope (feel) should be a clean disconnection.
The result of this, besides receiving an exception, is my app hangs, the UI is in total lock up, progress bars and everything mid way, with all signs pointing to having a race condition or WCF deadlock [sigh], but I am pretty thread-savvy now and I think this is a relatively isolated situation and reading the exception as-is, I don't think it's a 'thread' issue per-se, as it states more an issue of early disconnection which then spirals all my threads into mayhem, perhaps causing the lock up.
My Unsubscribe() approach on the client looks like this:
public void Unsubscribe()
{
try
{
// Close existing connections
if (channel != null &&
channel.State == CommunicationState.Opened)
{
proxy.Unsubscribe();
}
}
catch (Exception)
{
// This is where we receive the 'System.ServiceModel.ProtocolException'.
}
finally
{
Dispose();
}
}
And my Dispose() method, which should perform the clean disconnect:
public void Dispose()
{
// Dispose object
if (channel != null)
{
try
{
// Close existing connections
Close();
// Attempt dispose object
((IDisposable)channel).Dispose();
}
catch (CommunicationException)
{
channel.Abort();
}
catch (TimeoutException)
{
channel.Abort();
}
catch (Exception)
{
channel.Abort();
throw;
}
}
}
And the WCF service Subscription() counterpart and class attributes (for reference) on the windows service server (nothing tricky here and my exception occurs client side):
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
ConcurrencyMode = ConcurrencyMode.Multiple)]
public class TransferService : LoggableBase, ITransferServiceContract
{
public void Unsubscribe()
{
if (clients.ContainsKey(clientName))
{
lock (syncObj)
{
clients.Remove(clientName);
}
}
#if DEBUG
Console.WriteLine(" + {0} disconnected.", clientName);
#endif
}
...
}
Interface of:
[ServiceContract(
CallbackContract = typeof(ITransferServiceCallbackContract),
SessionMode = SessionMode.Required)]
public interface ITransferServiceContract
{
[OperationContract(IsInitiating = true)]
bool Subscribe();
[OperationContract(IsOneWay = true)]
void Unsubscribe();
...
}
Interface of callback contract, it doesn't do anything very exciting, just calls events via delegates etc. The reason I included this is to show you my attributes. I did alleviate one set of deadlocks already by including UseSynchronizationContext = false:
[CallbackBehavior(UseSynchronizationContext = false,
ConcurrencyMode = ConcurrencyMode.Multiple)]
public class TransferServiceCallback : ITransferServiceCallbackContract
{ ... }
Really hope somebody can help me! Thanks a lot =:)
OH my gosh, I found the issue.
That exception had nothing to do with the underyling app hang, that was just a precautionary exception which you can safely catch.
You would not believe it, I spent about 6 hours on and off on this bug, it turned out to be the channel.Close() locking up waiting for pending WCF requests to complete (which never would complete until the transfer has finished! which defeats the purpose!)
I just went brute-force breakpointing line after line, my issue was if I was too slow..... it would never hang, because somehow the channel would be available to close (even before the transfer had finished) so I had to breakpoint F5 and then quickly step to catch the hang, and that's the line it ended on. I now simply apply a timeout value to the Close() operation and catch it with a TimeoutException and then hard abort the channel if it cannot shut down in a timely fashion!
See the fix code:
private void Close()
{
if (channel != null &&
channel.State == CommunicationState.Opened)
{
// If cannot cleanly close down the app in 3 seconds,
// channel is locked due to channel heavily in use
// through callbacks or the like.
// Throw TimeoutException
channel.Close(new TimeSpan(0, 0, 0, 3));
}
}
public void Dispose()
{
// Dispose object
if (channel != null)
{
try
{
// Close existing connections
// *****************************
// This is the close operation where we perform
//the channel close and timeout check and catch the exception.
Close();
// Attempt dispose object
((IDisposable)channel).Dispose();
}
catch (CommunicationException)
{
channel.Abort();
}
catch (TimeoutException)
{
channel.Abort();
}
catch (Exception)
{
channel.Abort();
throw;
}
}
}
I am so happy to have this bug finally over and done with! My app is now shutting down cleanly after a 3 second timeout regardless of the current WCF service state, I hope I could have helped someone else who ever finds themselves suffering a similar issue.
Graham