I'm using Fiorano's C# support for JMS to publish messages on a topic.
Everything goes fine until the application exits. Then it doesn't actually exit.
I'm assuming that Fiorano is running a foreground thread (but that's a guess on my part.)
Here's a minimal, but complete, example that illustrates the problem:
using System;
using System.Collections;
using System.Diagnostics;
using System.Linq;
using fiorano.csharp.naming;
// Note: Reference to
// Assembly: fmq-csharp-native
// Version: v2.0.50727
// Runtime Version: 10.2.0.10533
namespace NotificationClientTest
{
/// <summary>
/// Main program
/// </summary>
public static class Program
{
/// <summary>
/// Main method
/// </summary>
/// <param name="args">The arguments. If any exist we hang.</param>
public static void Main(string[] args)
{
Report("Enter Main");
TheFioranoHangOnExit(args.Any());
Report("Leave Main");
}
/// <summary>
/// Trigger the problem.
/// </summary>
/// <param name="problem"> If true, trigger the problem </param>
private static void TheFioranoHangOnExit(bool problem)
{
// Initialize queue
var contextProperties = new Hashtable
{
{ FioranoContext.SECURITY_PRINCIPAL, "user" },
{ FioranoContext.SECURITY_CREDENTIALS, "secretPassword" },
{ FioranoContext.PROVIDER_URL, "http://192.168.5.1:1956" },
{ FioranoContext.BACKUP_URLS, "http://192.168.5.2:1956" },
{ FioranoContext.INITIAL_CONTEXT_FACTORY, "fiorano.jms.runtime.naming.FioranoInitialContextFactory" },
};
var namingContext = new FioranoNamingContext(contextProperties);
var topicFactory = namingContext.lookupTCF("PRIMARYTCF");
if (problem)
{
Report("Creating a connection");
var connection = topicFactory.createTopicConnection();
connection.stop(); // I've tried swapping the order of stop and close just in case...
connection.close();
}
else
{
Report("No Connection");
}
namingContext.close();
}
/// <summary>
/// Write to console, write to debug window
/// </summary>
/// <param name="message">What to say</param>
private static void Report(string message)
{
Console.WriteLine(message);
Debug.WriteLine(message);
}
}
}
Running this application
C:\Playground\FioranoHangTest\bin\Debug>NotificationClientTest.exe
Enter Main
No Connection
Leave Main
C:\Playground\FioranoHangTest\bin\Debug>NotificationClientTest.exe oops
Enter Main
Creating a connection
Leave Main
[At this point the program hangs.
^C or Task Manager can kill it.]
This question describes a similar problem encountered in Java with GlassFish's JMS. Does Fiorano/C# have the same problem?
Otherwise, what am I missing?
I found an ugly work-around:
public static void Main(string[] args)
{
Report("Enter Main");
TheFioranoHangOnExit(args.Any());
Report("Leave Main");
// Evict Fiorano Foreground threads.
Environment.Exit(0);
}
Excuse me, I have to go wash my fingers off with lye soap after typing that one.
I'm still hoping for a better answer.
Related
First off, I'm fairly new to WCF so please keep that in mind when responding.
The problem is I created a WCF library (AppCommWcf) that is shared between the client and the server. When I write a Console application using the AppCommWcf library and call it, it works fine. When I do the same thing for a test WinForms application it works fine, however, when I use it in our WinForms system application, the client can't connect to the server. Both client and server are running on my local machine. I am not using any values in their respective application.exe.config files. Before you go and use the code below to try and reproduce, as I said previously this works fine in smaller applications, but not our over a million lines of code application so I'm hoping the seasoned WCF professionals can tell me some things to look for as to why the client is failing. But all I'm doing is using the same exact code located below in between the 3 different applications and it only fails in our system application.
Tests I've run:
System app server running, System app client running, client fails to connect to server.
Console running as server, System app server running (WCF code commented out), System App client running, client connects to console server via WCF and completes method call.
Console running as client, System App server running, client fails to connect to server.
Based on the tests above something seems wrong with the server, but I can't figure out why it only fails in the System app server, but works in the Console and WinForms server test apps using the same client/server code in all of them.
Here's the code from the AppCommWcf library:
Project References:
System
System.Runtime.Serialization
System.ServiceModel
CameraCtrlCommClient.cs:
using System.ServiceModel;
namespace AppCommWcf
{
public partial class CameraCtrlCommClient : ClientBase<ICameraCtrlComm>, ICameraCtrlComm
{
public CameraCtrlCommClient(string bindUrl) : base(new WSHttpBinding(), new EndpointAddress(bindUrl)) { }
public LockRequestResponse RequestLock(LockTypeWcf lckType, string camName, int stationId, string userName, bool takeCtrlIfLocked)
{
return base.Channel.RequestLock(lckType, camName, stationId, userName, takeCtrlIfLocked);
}
}
}
CameraCtrlCommServer.cs:
using System;
using System.ServiceModel;
namespace AppCommWcf
{
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class CameraCtrlCommServer : ICameraCtrlComm
{
public delegate LockRequestResponse LockEventHandler(LockTypeWcf lckType, string camName, int stationId, string userName, bool takeCtrlIfLocked);
public event LockEventHandler LockRequested;
static ServiceHost m_svcHost;
CameraCtrlCommServer() { }
public string BindingUrl { get; private set; }
public void Close()
{
if (m_svcHost != null)
{
m_svcHost.Close();
m_svcHost = null;
}
}
public LockRequestResponse RequestLock(LockTypeWcf lckType, string camName, int stationId, string userName, bool takeCtrlIfLocked)
{
return LockRequested.Invoke(lckType, camName, stationId, userName, takeCtrlIfLocked);
}
public static CameraCtrlCommServer StartServiceHost(string bindUrl)
{
CameraCtrlCommServer result = null;
if (m_svcHost == null)
{
result = new CameraCtrlCommServer { BindingUrl = bindUrl };
m_svcHost = new ServiceHost(result, new Uri(bindUrl))
{
OpenTimeout = new TimeSpan(0, 0, 30),
CloseTimeout = new TimeSpan(0, 0, 10)
};
m_svcHost.AddServiceEndpoint(typeof(ICameraCtrlComm), new WSHttpBinding(), bindUrl);
m_svcHost.Faulted += OnSvcHostFaulted;
m_svcHost.Opened += OnSvcHostOpened;
m_svcHost.Closing += OnSvcHostClosing;
m_svcHost.Closed += OnSvcHostClosed;
m_svcHost.UnknownMessageReceived += OnSvcHostUnknownMsgRecvd;
m_svcHost.Open();
}
else
{
throw new Exception("The service host has already been started.");
}
return result;
}
static void OnSvcHostUnknownMsgRecvd(object sender, UnknownMessageReceivedEventArgs e)
{
}
static void OnSvcHostClosed(object sender, EventArgs e)
{
}
static void OnSvcHostClosing(object sender, EventArgs e)
{
}
static void OnSvcHostOpened(object sender, EventArgs e)
{
}
static void OnSvcHostFaulted(object sender, EventArgs e)
{
}
}
}
Enums.cs:
using System.Runtime.Serialization;
namespace AppCommWcf
{
/// <summary>
/// The type of lock being requested. These values must match BMS.ElectroOptic.C4IAppOffEnum.BMSSensorLockTypeEnum
/// </summary>
[DataContract(Name = "LockTypeWcf", Namespace = "http://schemas.datacontract.org/2004/07/AppCommWcf")]
public enum LockTypeWcf : int
{
/// <summary>
/// The camera is not locked.
/// </summary>
[EnumMember]
NotLocked = 0,
/// <summary>
/// A manual lock was performed by clicking the Lock toolbar button on a video, cannot be overridden except by another
/// user or an <see cref="Entity"/> lock.
/// </summary>
[EnumMember]
Manual,
/// <summary>
/// An action was performed that required an auto-lock and an auto-unlock if not other action is taken within
/// the time specified in C4IAppSimple.BMSSEOMngrParamsOfRoot.AutoUnlockTimeout. Can be overridden by another user
/// or <see cref="Manual"/> or <see cref="Entity"/>.
/// </summary>
[EnumMember]
Auto,
/// <summary>
/// A lock was performed by an entity action, i.e. House Call, Line Scan or Investigation. Can be overridden by
/// another user or <see cref="Manual"/> or <see cref="Auto"/>.
/// </summary>
[EnumMember]
Entity
}
#region LockPromptResponse enum
/// <summary>
/// The possible responses from <see cref="BMSEOMngr.RequestCameraLock(BMSSensorLockTypeEnum, BMSEOChannelEnt, int, string)"/>
/// or <see cref="BMSEOMngr.RequestCameraLock(BMSSensorLockTypeEnum, InfSensorId, int, string)"/>.
/// </summary>
[DataContract(Name = "LockRequestResponse", Namespace = "http://schemas.datacontract.org/2004/07/AppCommWcf")]
public enum LockRequestResponse
{
/// <summary>
/// The video is not locked.
/// </summary>
[EnumMember]
NotLocked = 0,
/// <summary>
/// The video is locked by the current user.
/// </summary>
[EnumMember]
LockedByCurrentUser,
/// <summary>
/// The video is locked by a different user.
/// </summary>
[EnumMember]
LockedByOtherUser
}
#endregion
}
ICameraCtrlComm.cs:
using System.ServiceModel;
namespace AppCommWcf
{
[ServiceContract]
public interface ICameraCtrlComm
{
[OperationContract(Action = "http://tempuri.org/ICameraLockComm/RequestLock", ReplyAction = "http://tempuri.org/ICameraLockComm/RequestLockResponse")]
LockRequestResponse RequestLock(LockTypeWcf lckType, string camName, int stationId, string userName, bool takeCtrlIfLocked);
}
}
Server calling code
using AppCommWcf;
CameraCtrlCommServer m_camCtrlServer;
Main()
{
m_camCtrlServer = CameraCtrlCommServer.StartServiceHost(bindUrl);
m_camCtrlServer.LockRequested += OnServerLockRequested;
}
LockRequestResponse OnServerLockRequested(LockTypeWcf lckType, string camName, int stationId, string userName, bool takeCtrlIfLocked)
{
return LockRequestResponse.LockedByCurrentUser;
}
Client calling code:
using AppCommWcf;
using System.ServiceModel.Channels;
CameraCtrlCommClient m_camCtrlClient;
void Main()
{
TimeSpan timeout = new TimeSpan(0, 0, 15); //So I don't have to wait a minute to find out it failed to connect.
m_camCtrlClient = new CameraCtrlCommClient(bindUrl);
Binding binding = m_camCtrlClient.Endpoint.Binding;
binding.OpenTimeout = timeout;
binding.ReceiveTimeout = timeout;
binding.SendTimeout = timeout;
}
Here's the screenshots of the WCF logs, hopefully it will help, if you want details of a given line let me know and I'll post it in the comments as I don't have enough space here to post the entire log.
Server Log:
Client Log:
The exception text is:
The HTTP request to 'http://localhost:10821/CameraCtrlComm' has exceeded the allotted timeout of 00:00:14.9990000. The time allotted to this operation may have been a portion of a longer timeout.
The problem ended up being I was instantiating the ServiceHost on a non-windows message loop thread. Once I created my own thread and used Application.Run for it to wait in that thread until the application exited, it worked. I hope this helps someone else out.
I have created an object which is used to control a piece of test equipment (oscilloscope) which communicates using the Visa library. This object (scope object) works fine, but there is one method I created to query the scope for a waveform average, which takes a while to execute (around a second or so), and blocks execution of the UI while it is acquiring the data.
To get around this problem, I initially tried creating a task object and using this task object to execute the function that is querying the scope for the data; but I found that something in the Visa driver object itself was apparently still executing on the main thread (and thus slowing down my UI).
I then did one more test and created a new thread, and had this thread call a function. Inside this function, I initialized the scope object, setup the operating parameters, and then called the long-running function. This time, my UI was as responive as normal with no slowdowns.
So now, It seems that I need to actually initialize the scope object inside a new thread in order to get it to truly run asynchronously. But now I have a new challenge. I need to access the objects properties and methods from the main thread to set things up, query status info, etc. Is there a clean way to effectively read and write to a class's properties and methods from another thread? Or are there any existing libraries available to make this simpler?
My current idea is to create a wrapper class for the scope object and then have this wrapper class initialize the scope object in a new thread. But I'm not sure of the best way to access the object's members efficiently. Or better yet, is there a better approach to this problem?
EDIT: Below is some more information and code for the test program I wrote. The UI is a just a simple form with Acquire and Connect buttons, and two labels (one for showing the measurements and the other shows a number that gets incremented as I click on the "Click" button:
Here's the code for the Scope Object I created:
using System;
using Ivi.Scope.Interop;
using Tektronix.Tkdpo2k3k4k.Interop;
using System.Diagnostics;
namespace Test_App
{
public class DPO4034
{
#region [NOTES] Installing TekVisa Drivers for DPO4034
/*
1. Download and install the TekVisa Connectivity Software from here:
https://www.tek.com/oscilloscope/tds7054-software/tekvisa-connectivity-software-v411
2. Check under Start -> All Programs -> TekVisa and see if the "Open Choice Installation Manager" shortcut works.If not, then update all shortcuts to point to the correct base folder for the TekVISA files, which is "C:\Program Files\IVI Foundation\VISA\".
3. Download the DPO4000 series IVI driver from here:
https://www.tek.com/oscilloscope/dpo4054-software/dpo2000-dpo3000-dpo4000-ivi-driver
4. After running the unzip utility, open the unzipped folder and goto x64 -> Shared Components, and run the IviCleanupUtility_2.0.0.exe utility to make sure no shared IVI components exist.
5. Run the IviSharedComponents64_2.1.1.exe file to install shared components.
6. Go up one folder and open the IVI Driver Folder and run the Tkdpo2k3k4k-x64.msi installer to install the scope IVI driver.
7. In the VS project, add references to the following COM components:
• IviDriverLib
• IviScopeLib
• Tkdpo2k3k4kLib
8. Right Click on each of the three references in the Solution Explorer and select Properties in the menu. When the properties window appears, set the "Embed Interop Types" property to False.
*/
#endregion
#region Class Variables
Tkdpo2k3k4kClass driver; // IVI Driver representing the DPO4034
IIviScope scope; // IVI Scope object representing the DPO4034
#endregion
#region Class Constructors
public DPO4034()
{
this.driver = new Tkdpo2k3k4kClass();
this.scope = (IIviScope)driver;
}
~DPO4034()
{
this.Disconnect();
}
#endregion
#region Public Properties
/// <summary>
/// Returns true if the scope is connected (initialized)
/// </summary>
public bool Connected
{
get
{
return this.driver.IIviDriver_Initialized;
}
}
#endregion
#region Public Methods
/// <summary>
/// Initializes the connection to the scope
/// <paramref name="reset"/>Resets the scope after connecting if set to true</param>
/// </summary>
/// <returns>True if the function succeeds</returns>
public bool Connect(bool reset = false)
{
try
{
if (!this.Connected)
{
this.Disconnect();
}
this.driver.Initialize("TCPIP::10.10.0.200::INSTR", true, reset, "Simulate=false, DriverSetup= Model=DPO4034");
return true;
}
catch (Exception ex)
{
PrintError(ex, "Connect");
return false;
}
}
/// <summary>
/// Closes the connection to the scope
/// </summary>
/// <returns>True if the function succeeds</returns>
public bool Disconnect()
{
try
{
if (this.Connected)
{
this.driver.Close();
}
return true;
}
catch (Exception ex)
{
PrintError(ex, "Disconnect");
return false;
}
}
/// <summary>
/// Reads the average value of the waveform on the selected channel
/// </summary>
/// <param name="channel">1-4 for channels 1 to 4</param>
/// <returns>The measured average value</returns>
public double ReadWaveformAverage(int channel)
{
if (this.Connected)
{
try
{
double value = 0;
this.scope.Measurements.Item["CH" + channel.ToString()].FetchWaveformMeasurement(IviScopeMeasurementEnum.IviScopeMeasurementVoltageAverage, ref value);
return value;
}
catch (Exception ex)
{
PrintError(ex, "ReadWaveformAverage");
return 0;
}
}
else
{
PrintError("Oscilloscope not connected", "ReadWaveformAverage");
return 0;
}
}
#endregion
#region Private Methods
/// <summary>
/// Prints an error message to the debug console
/// </summary>
/// <param name="err">Error object</param>
/// <param name="source">Source of the error</param>
private void PrintError(Exception err, string source = "") //, bool showMessageBox = false)
{
Debug.Print($"Error: {err.Message}");
Debug.Print($"Source: {source}");
}
/// <summary>
/// Prints an error message to the debug console
/// </summary>
/// <param name="err">Error object</param>
/// <param name="source">Source of the error</param>
private void PrintError(string error, string source = "")
{
Debug.Print($"Error: {error}");
Debug.Print($"Source: {source}");
}
#endregion
}
}
Here's the code for the version of the form that uses an async function and tasks to directly call the acquisition functions:
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Test_App
{
public partial class Form1 : Form
{
byte number = 0;
public Form1()
{
InitializeComponent();
}
private void cmdAcquire_Click(object sender, EventArgs e)
{
takeMeasurements();
}
async void takeMeasurements()
{
try
{
// Create new instance of the scope object and connect to it
DPO4034 Scope = new DPO4034();
Scope.Connect();
// Update status
PrintStatus(Scope.Connected ? "Connected" : "Error");
// Loop continuously and print the samples to the status label
while (Scope.Connected)
{
double inputVoltage = await Task.Run(() => Scope.ReadWaveformAverage(1));
double inputCurrent = await Task.Run(() => Scope.ReadWaveformAverage(2));
double outputVoltage = await Task.Run(() => Scope.ReadWaveformAverage(3));
PrintStatus($"CH1: {inputVoltage}\n" +
$"CH2: {inputCurrent}\n" +
$"CH3: {outputVoltage}\n");
}
}
catch (Exception)
{
PrintStatus("Error");
}
}
private void cmdIncrement(object sender, EventArgs e)
{
// This is just something for me to make the interface do to see
// how responsive it is
lblNumber.Text = number.ToString();
number++;
}
// Prints status text to the label on the form
private void PrintStatus(string text)
{
Status.Text = text;
}
}
}
and here's the code for the version of the form that uses a separate thread to run the scope object:
using System;
using System.Threading;
using System.Windows.Forms;
namespace Test_App
{
public partial class Form1 : Form
{
Thread t;
byte number = 0;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
t = new Thread(new ThreadStart(takeMeasurements));
}
private void cmdAcquire_Click(object sender, EventArgs e)
{
t.Start();
}
// Function to create scope object and take acquisitions
void takeMeasurements()
{
try
{
// Create new instance of the scope object and connect to it
DPO4034 Scope = new DPO4034();
Scope.Connect();
// Update status
PrintStatus(Scope.Connected ? "Connected" : "Error");
// Loop continuously and print the samples to the status label
while (Scope.Connected)
{
double inputVoltage = Scope.ReadWaveformAverage(1);
double inputCurrent = Scope.ReadWaveformAverage(2);
double outputVoltage = Scope.ReadWaveformAverage(3);
PrintStatus($"CH1: {inputVoltage}\n" +
$"CH2: {inputCurrent}\n" +
$"CH3: {outputVoltage}\n");
}
}
catch (Exception)
{
PrintStatus("Error");
}
}
private void cmdIncrement(object sender, EventArgs e)
{
// This is just something for me to make the interface do to see
// how responsive it is
lblNumber.Text = number.ToString();
number++;
}
// Prints status text to the label on the form
private void PrintStatus(string text)
{
if (!this.IsDisposed)
{
this.BeginInvoke((MethodInvoker)delegate
{
Status.Text = text;
});
}
else
{
t.Abort();
}
}
}
}
I hope this gives some more insight into what I'm trying to accomplish. Thank you all for your comments and I look forward to your feedback.
EDIT2: Just to be more clear, the method I would prefer to use (if possible) is the one using tasks. In the current program, the Scope object is initialized at the top of the form on the main thread and accessed by multiple objects within the program.
For anyone interested, I finally found a solution to the problem I was having with the GUI freezing when executing the ReadWaveformData() function.
The answer was to create a new thread inside of the Scope class that would call an Initialization function to initialize the internal scope and driver objects. Then the thread would do nothing but sit and host the instances until the ReadWavveformData() function is called inside a task. Here's the modified DPO4034 class:
using System;
using Ivi.Scope.Interop;
using Tektronix.Tkdpo2k3k4k.Interop;
using System.Diagnostics;
using System.Threading;
namespace Test_App
{
public class DPO4034
{
#region [NOTES] Installing TekVisa Drivers for DPO4034
/*
1. Download and install the TekVisa Connectivity Software from here:
https://www.tek.com/oscilloscope/tds7054-software/tekvisa-connectivity-software-v411
2. Check under Start -> All Programs -> TekVisa and see if the "Open Choice Installation Manager" shortcut works.If not, then update all shortcuts to point to the correct base folder for the TekVISA files, which is "C:\Program Files\IVI Foundation\VISA\".
3. Download the DPO4000 series IVI driver from here:
https://www.tek.com/oscilloscope/dpo4054-software/dpo2000-dpo3000-dpo4000-ivi-driver
4. After running the unzip utility, open the unzipped folder and goto x64 -> Shared Components, and run the IviCleanupUtility_2.0.0.exe utility to make sure no shared IVI components exist.
5. Run the IviSharedComponents64_2.1.1.exe file to install shared components.
6. Go up one folder and open the IVI Driver Folder and run the Tkdpo2k3k4k-x64.msi installer to install the scope IVI driver.
7. In the VS project, add references to the following COM components:
• IviDriverLib
• IviScopeLib
• Tkdpo2k3k4kLib
8. Right Click on each of the three references in the Solution Explorer and select Properties in the menu. When the properties window appears, set the "Embed Interop Types" property to False.
*/
#endregion
#region Class Variables
Tkdpo2k3k4kClass driver; // IVI Driver representing the DPO4034
IIviScope scope; // IVI Scope object representing the DPO4034
Thread t; // Thread to initialize the scope objects in to ensure that they async method calls do not run on the main thread
#endregion
#region Class Constructors
public DPO4034()
{
t = new Thread(new ThreadStart(Initialize));
t.Start();
// Wait for scope object to be initialized in the thread
while (this.scope == null);
}
~DPO4034()
{
this.Disconnect();
t.Abort();
}
#endregion
#region Public Properties
/// <summary>
/// Returns true if the scope is connected (initialized)
/// </summary>
public bool Connected
{
get
{
return this.driver.IIviDriver_Initialized;
}
}
#endregion
#region Public Methods
/// <summary>
/// Initializes the connection to the scope
/// <paramref name="reset"/>Resets the scope after connecting if set to true</param>
/// </summary>
/// <returns>True if the function succeeds</returns>
public bool Connect(bool reset = false)
{
try
{
if (!this.Connected)
{
this.Disconnect();
}
this.driver.Initialize("TCPIP::10.10.0.200::INSTR", true, reset, "Simulate=false, DriverSetup= Model=DPO4034");
return true;
}
catch (Exception ex)
{
PrintError(ex, "Connect");
return false;
}
}
/// <summary>
/// Closes the connection to the scope
/// </summary>
/// <returns>True if the function succeeds</returns>
public bool Disconnect()
{
try
{
if (this.Connected)
{
this.driver.Close();
}
return true;
}
catch (Exception ex)
{
PrintError(ex, "Disconnect");
return false;
}
}
/// <summary>
/// Reads the average value of the waveform on the selected channel
/// </summary>
/// <param name="channel">1-4 for channels 1 to 4</param>
/// <returns>The measured average value</returns>
public double ReadWaveformAverage(int channel)
{
if (this.Connected)
{
try
{
double value = 0;
this.scope.Measurements.Item["CH" + channel.ToString()].FetchWaveformMeasurement(IviScopeMeasurementEnum.IviScopeMeasurementVoltageAverage, ref value);
return value;
}
catch (Exception ex)
{
PrintError(ex, "ReadWaveformAverage");
return 0;
}
}
else
{
PrintError("Oscilloscope not connected", "ReadWaveformAverage");
return 0;
}
}
#endregion
#region Private Methods
private void Initialize()
{
this.driver = new Tkdpo2k3k4kClass();
this.scope = (IIviScope)driver;
// Does nothing but allow the objects to exist on the separate thread
while (true)
{
Thread.Sleep(int.MaxValue);
}
}
/// <summary>
/// Prints an error message to the debug console
/// </summary>
/// <param name="err">Error object</param>
/// <param name="source">Source of the error</param>
private void PrintError(Exception err, string source = "") //, bool showMessageBox = false)
{
Debug.Print($"Error: {err.Message}");
Debug.Print($"Source: {source}");
}
/// <summary>
/// Prints an error message to the debug console
/// </summary>
/// <param name="err">Error object</param>
/// <param name="source">Source of the error</param>
private void PrintError(string error, string source = "")
{
Debug.Print($"Error: {error}");
Debug.Print($"Source: {source}");
}
#endregion
}
}
If this is paired up with the version of the TestApp that uses the async tasks to execute the ReadWaveformData() function, then things run smoothly and I don't need to completely rewrite the scope class to get it to work in my program. Hope this is helpful for anyone else who may run into a similar challenge.
Server start up error when trying to add a purger timer ( game feature) seems that the server is not allowed access?
PurgeTimer:
using System;
using System.Threading;
using System.Collections.Concurrent;
using Plus.HabboHotel.GameClients;
using Plus.HabboHotel.Rooms;
namespace Plus.HabboHotel.Minigames.Purge
{
/// <summary>
/// This will do a countdown before the match starts
/// </summary>
public class PurgeTimer
{
/// <summary>
/// Timer for our operation
/// </summary>
private Timer Timer;
public bool On = false;
/// <summary>
/// Constructor
/// </summary>
public PurgeTimer()
{
// Method to call when completed
TimerCallback TimerCallback = Ticked;
// Create a new instance of timer
Timer = new Timer(TimerCallback, null, 30000, Timeout.Infinite);
}
/// <summary>
/// Method is call when timer is finished
/// </summary>
/// <param name="info">The information</param>
public void Ticked(object info)
{
try
{
if (PurgeManager.Running)
{
foreach (GameClient client in PlusEnvironment.GetGame().GetClientManager()._clients.Values)
{
try
{
if (client == null)
{
continue;
}
client.SendWhisper("[Automatic Event Alert]: The hotel is currently under Purge Mode. All crime is legal.");
}
catch (Exception e)
{
}
}
Timer.Change(30000, Timeout.Infinite);
}
else
{
return;
}
}
catch { }
}
}
}
PurgeManager:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Plus.HabboHotel.Minigames.Purge;
using System.Threading.Tasks;
namespace Plus.HabboHotel.Minigames.Purge
{
public class PurgeManager
{
public static PurgeTimer MainTimer;
public static bool Running;
}
}
Error:
http://prntscr.com/9ss0qb I don't get while it's not accessible!
Firstly, please post the error directly into the question, not as a linked image.
The error Plus.HabboHotel.GameClients.GameClientManager._clients is inaccessible due to its protection level seems pretty clear. From the naming convention used I'd take a guess that the _clients collection is private.
Here is your problem.
From your GameClientManager class, you're trying to access a private field.
private ConcurrentDictionary<int, GameClient> _clients;
For more details regarding your problem, review this. What are Access Modifiers in C#?
I am having a kinda annoying problem mostly due to my low skill level/experience in C# multithreading.
Here is the background. In my framework, I have a static class named WaitFormHelper, which has two static methods (well... actually more but we don't care here), Start() and Close()
The Start() method initializes and starts a thread which will acquire a lock on the locker object and create a WaitForm (which is a small loading control with a custom message and a progress bar)
In my current project, I have a method which starts a WaitForm, performs calculations, then closes the WaitForm. Nothing fancy at all.
The method looks like the following, I simplified it as much as possible:
public void PerformCalculations()
{
try
{
WaitFormHelper.Start("Title", "message", false);
if (this.CalculationsParameters.IsInvalid)
{
return;
}
// Perform all those lengthy calculations here
}
// catch whatever exception I may have to catch, we don't care here
finally
{
WaitFormHelper.Close();
}
}
Here are the Start() and Close() methods with related methods & attributes, simplified as well:
private static Thread instanceCaller;
private static WaitForm instance;
private static AutoResetEvent waitFormStarted = new AutoResetEvent(false);
private static object locker = new object();
/// <summary>
/// Initializes WaitForm to start a single task
/// </summary>
/// <param name="header">WaitForm header</param>
/// <param name="message">Message displayed</param>
/// <param name="showProgressBar">True if we want a progress bar, else false</param>
public static void Start(string header, string message, bool showProgressBar)
{
InitializeCallerThread(showProgressBar, header, message);
instanceCaller.Start();
}
/// <summary>
/// Initializes caller thread for executing a single command
/// </summary>
/// <param name="showProgressBar"></param>
/// <param name="header"></param>
/// <param name="message"></param>
private static void InitializeCallerThread(bool showProgressBar, string header, string message)
{
waitFormStarted.Reset();
instanceCaller = new Thread(() =>
{
lock (locker)
{
instance = new WaitForm()
{
Header = header,
Message = message,
IsProgressBarVisible = showProgressBar
};
waitFormStarted.Set();
}
instance.ShowDialog();
});
instanceCaller.Name = "WaitForm thread";
instanceCaller.SetApartmentState(ApartmentState.STA);
instanceCaller.IsBackground = true;
}
/// <summary>
/// Closes current form
/// </summary>
public static void Close()
{
lock (locker)
{
if (instance != null && !instance.IsClosed)
{
waitFormStarted.WaitOne();
instance.FinalizeWork();
instance.Dispatcher.Invoke(
new Action(() =>
{
instance.Close();
}));
}
}
}
Now let's get to the problem
This usually works fine, except in this case:
If this.CalculationsParameters.IsInvalid is true (ie. as you probably already understood, I can't perform calculations for some reason, such as "user entering crap in my forms"), the execution directly closes my WaitForm.
However in this case, the main thread will reach the Close method and acquire a lock on the locker object before the thread fired by the Start() method.
What happens is that: Close acquires lock, tries to close the form but instance is still null because the Thread created in InitializeCallerThread is still waiting for the lock to actually create it. Close releases lock, InitializeCallerThread acquires it and... shows a WaitForm which will not close.
Now I know that I can simply fix this problem by testing if the calculation parameters are invalid before actually starting the WaitForm, but well, problem is this WaitForm is supposed to be used by all applications of our framework (which includes 40+ different apps used and maintained in 4 countries), so ideally I'd rather prefer seeing my WaitForm working at all cases.
Do you have any idea on how could I synchronize this to make sure the starter thread is definitely called and executed first?
As you can see I already use an AutoResetEvent for this matter, but if I listen to it before the test if (instance != null) I will end up stuck in my case.
Hope this is clear enough! Thank you!
You need some mechanism to make a "queue control", coordinating steps according to the order you want them occurring.
Recently I have needed to implement something like this to force a specific order in the execution of several threads in a unit test.
Here is my suggestion:
QueueSynchronizer:
/// <summary>
/// Synchronizes steps between threads.
/// </summary>
public class QueueSynchronizer
{
/// <summary>
/// Constructor.
/// </summary>
/// <param name="minWait">Minimum waiting time until the next try.</param>
/// <param name="maxWait">Maximum waiting time until the next try.</param>
public QueueSynchronizer(Int32 minWait, Int32 maxWait)
{
}
private Mutex mx = new Mutex();
/// <summary>
/// Minimum waiting time until the next try.
/// </summary>
private Int32 minWait = 5;
/// <summary>
/// Maximum waiting time until the next try.
/// </summary>
private Int32 maxWait = 500;
int currentStep = 1;
/// <summary>
/// Key: order in the queue; Value: Time to wait.
/// </summary>
private Dictionary<int, int> waitingTimeForNextMap = new Dictionary<int, int>();
/// <summary>
/// Synchronizes by the order in the queue. It starts from 1. If is not
/// its turn, the thread waits for a moment, after that, it tries again,
/// and so on until its turn.
/// </summary>
/// <param name="orderInTheQueue">Order in the queue. It starts from 1.</param>
/// <returns>The <see cref="Mutex"/>The mutex that must be released at the end of turn.
/// </returns>
public Mutex Sincronize(int orderInTheQueue)
{
do
{
//while it is not the turn, the thread will stay in this loop and sleeping for 100, 200, ... 1000 ms
if (orderInTheQueue != this.currentStep)
{
//The next in queue will be waiting here (other threads).
mx.WaitOne();
mx.ReleaseMutex();
//Prevents 100% processing while the current step does not happen
if (!waitingTimeForNextMap.ContainsKey(orderInTheQueue))
{
waitingTimeForNextMap[orderInTheQueue] = this.minWait;
}
Thread.Sleep(waitingTimeForNextMap[orderInTheQueue]);
waitingTimeForNextMap[orderInTheQueue] = Math.Min(waitingTimeForNextMap[orderInTheQueue] * 2, this.maxWait);
}
} while (orderInTheQueue != this.currentStep);
mx.WaitOne();
currentStep++;
return mx;
}
}
Start() and Close() with QueueSynchronizer:
//synchronizer
private static QueueSynchronizer queueSynchronizer;
private static Thread instanceCaller;
private static WaitForm instance;
private static AutoResetEvent waitFormStarted = new AutoResetEvent(false);
private static object locker = new object();
/// <summary>
/// Initializes WaitForm to start a single task
/// </summary>
/// <param name="header">WaitForm header</param>
/// <param name="message">Message displayed</param>
/// <param name="showProgressBar">True if we want a progress bar, else false</param>
public static void Start(string header, string message, bool showProgressBar)
{
queueSynchronizer = new QueueSynchronizer();
InitializeCallerThread(showProgressBar, header, message);
instanceCaller.Start();
}
/// <summary>
/// Initializes caller thread for executing a single command
/// </summary>
/// <param name="showProgressBar"></param>
/// <param name="header"></param>
/// <param name="message"></param>
private static void InitializeCallerThread(bool showProgressBar, string header, string message)
{
waitFormStarted.Reset();
instanceCaller = new Thread(() =>
{
lock (locker)
{
//Queuing to run on first.
Mutex mx = queueSynchronizer.Sincronize(1);
try
{
instance = new WaitForm()
{
Header = header,
Message = message,
IsProgressBarVisible = showProgressBar
};
}
finally
{
//I think is here that ends the first step!?
mx.ReleaseMutex();
}
waitFormStarted.Set();
}
instance.ShowDialog();
});
instanceCaller.Name = "WaitForm thread";
instanceCaller.SetApartmentState(ApartmentState.STA);
instanceCaller.IsBackground = true;
}
/// <summary>
/// Closes current form
/// </summary>
public static void Close()
{
//Queuing to run on second.
Mutex mx = queueSynchronizer.Sincronize(2);
try
{
lock (locker)
{
if (instance != null && !instance.IsClosed)
{
waitFormStarted.WaitOne();
instance.FinalizeWork();
instance.Dispatcher.Invoke(
new Action(() =>
{
instance.Close();
}));
}
}
}
finally
{
mx.ReleaseMutex();
}
}
You need to Join on the thread that you created. By joining on a thread you'll block at that point until the thread has finished executing.
public static void Close()
{
lock (locker)
{
instanceCaller.Join();
if (instance != null && !instance.IsClosed)
{
waitFormStarted.WaitOne();
instance.FinalizeWork();
instance.Dispatcher.Invoke(
new Action(() =>
{
instance.Close();
}));
}
}
}
Overview of the problem:
I try to use a thread (while..loop) to listen a command from user. If user send a command, it will assign new value in the global variable which is in the class (LoopingWorkerThread).
I don't understand if I don't put the thread sleep value lower than 10 milliseconds, and I wouldn't get any response (it is in the ListenCommand method) . Look like the global parameter is being overwritten "_CommandReceived" in the method, probably the processor run to fast and ignore the value of the parameter had changed ("_CommandReceived").
Kindly comment if there is any better mechanism. I had lock it in the ListenCommand while loop.
The following are the codes:
public class LoopingWorkerThread
{
/// <summary>
/// Local main thread for LoopingWorkerThread
/// </summary>
private Thread t;
/// <summary>
/// Local parameter to identify the LoopingWorkerThread Is On
/// </summary>
private bool _IsOn;
/// <summary>
/// Local parameter to store command received from user
/// </summary>
private int _CommandReceived;
/// <summary>
/// Local object to use for locking the LoopingWorker Thread
/// </summary>
private object _LockListenCommand = new object();
/// <summary>
/// Properties of LoopingWorker Thread Is On
/// </summary>
public bool IsOn
{
get { return _IsOn; }
set { _IsOn = value; }
}
/// <summary>
/// Property of storing the command received from user
/// </summary>
public int CommandReceived
{
get { return _CommandReceived; }
set { _CommandReceived = value; }
}
/// <summary>
/// Delegate for OnResponse Event Handler
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public delegate void OnResponseHandler(object sender, ResponseArg e);
/// <summary>
/// Event of OnResponse
/// </summary>
public event OnResponseHandler OnResponse;
/// <summary>
/// Constructor of LoopingWorkerThread Class
/// </summary>
public LoopingWorkerThread()
{
_IsOn = false;
}
/// <summary>
/// Method of LoopingWorkerThread Function
/// </summary>
private void ListenCommand()
{
lock (_LockListenCommand)
while (_IsOn)
{
switch (_CommandReceived)
{
case 0:
// Ignore default command
break;
case 1:
FireOnResponse("Received cmd 1, response [Hello One]");
break;
case 2:
FireOnResponse("Received cmd 2, response [Hello Two]");
break;
default:
FireOnResponse("Error. Received unidentified command - " + _CommandReceived.ToString());
break;
}
//Console.WriteLine("ThreadProc: Cmd:[{0}] - Response:{1}", _CommandReceived.ToString(), ReaderResponse);
// Reset or Clear the Command Received
_CommandReceived = 0;
// If the sleep less than 10 millisecond, it always don't catch the
// command received which assigned to 1 or 2. Don't understand, or is there
// any better method.
**Thread.Sleep(10);**
}
}
/// <summary>
/// Function of firing response event back to user
/// </summary>
/// <param name="message"></param>
private void FireOnResponse(string message)
{
ResponseArg myarg = new ResponseArg(message);
if (OnResponse != null)
OnResponse(this, myarg);
}
/// <summary>
/// Method of starting the LoopingWorkerThread
/// </summary>
public void Start()
{
_IsOn = true;
FireOnResponse("Main thread: Started.");
// The constructor for the Thread class requires a ThreadStart
// delegate that represents the method to be executed on the
// thread. C# simplifies the creation of this delegate.
t = new Thread(new ThreadStart(ListenCommand));
// Start ThreadProc. Note that on a uniprocessor, the new
// thread does not get any processor time until the main thread
// is preempted or yields. Uncomment the Thread.Sleep that
// follows t.Start() to see the difference.
t.Start();
//Thread.Sleep(0);
FireOnResponse("Main thread: Call Start().");
}
/// <summary>
/// Method of stopping the LoopingWorkerThread
/// </summary>
public void Stop()
{
_IsOn = false;
t.Join();
//t.Abort();
FireOnResponse("LoopingWorker Thread is stopped.");
}
/// <summary>
/// Method of sending command to the LoopingWorkerThread
/// </summary>
/// <param name="readercmd"></param>
public void SendCommand(int readercmd)
{
_CommandReceived = readercmd;
}
}
Your code works because Thread.Sleep produces the necessary memory barrier required to read _commandReceived correctly. If you remove the Thread.Sleep call then you also remove the implicit memory barrier. Obviously, this is not a good mechanism to rely on though.
More importantly you are going about this the wrong way. What you should be using is the producer-consumer pattern. This is pretty easy with the BlockingCollection class since it blocks the consumer on Take while the queue is empty.
public class Example
{
private BlockingCollection<int> commands = new BlockingCollection<int>();
public Example()
{
var thread = new Thread(Run);
thread.IsBackground = true;
thread.Start();
}
public void SendCommmand(int command)
{
commands.Add(command);
}
private void Run()
{
while (true)
{
int command = commands.Take();
ProcessCommand(command);
}
}
private void ProcessCommand(int command)
{
// Process the command here.
}
}
BlockingCollection is available for 3.5 as part of the Reactive Extensions download.
Try declaring the variable volatile. More about this on http://msdn.microsoft.com/en-us/library/x13ttww7.aspx