Unit Testing NAudio with ManualResetEvent Locks Up Test - c#

Alright, I'm trying to unit test NAudio against a wrapper I created for a recording session, here is the code that starts and stops a recording session ...
public void StartRecording(string claimNo, string ip_no, string ip_name)
{
if (this.IsRecording)
{
return;
}
this.Recordings.Add(new RecordingTrack(claimNo, ip_no, ip_name));
if (this.MicrophoneLevel == default(float))
{
this.MicrophoneLevel = .75f;
}
_aggregator.Reset();
_input = new WaveIn();
_input.WaveFormat = _waveFormat;
_input.DataAvailable += (s, args) =>
{
_writer.Write(args.Buffer, 0, args.BytesRecorded);
byte[] buffer = args.Buffer;
for (int index = 0; index < args.BytesRecorded; index += 2)
{
short sample = (short)((buffer[index + 1] << 8) | buffer[index + 0]);
float sample32 = sample / 32768f;
_aggregator.Add(sample32);
}
if (this.DataAvailable != null)
{
this.DataAvailable(s, args);
}
if (!this.IsRecording)
{
_writer.Close();
_writer.Dispose();
_writer = null;
}
};
_input.RecordingStopped += (s, args) =>
{
_input.Dispose();
_input = null;
if (this.RecordingStopped != null)
{
this.RecordingStopped(s, args);
}
};
_writer = new WaveFileWriter(this.CurrentRecording.FileName, _input.WaveFormat);
_input.StartRecording();
this.IsRecording = true;
}
public void StopRecording()
{
if (!this.IsRecording)
{
return;
}
this.CurrentRecording.Stop();
this.IsRecording = false;
_input.StopRecording();
}
... and below is my unit test. I'm using a ManualResetEvent to assert the success of the event being fired and it's declared like this ...
private ManualResetEvent _eventRaised = new ManualResetEvent(false);
... however, the issue is that the test below simply locks up and the event is never fired. Can you confirm that the issue is that the WaitOne is not allowing the event to fire because it's locking the same thread?
bool success = false;
_eventRaised.Reset();
var target = new RecordingSession();
target.StartRecording("1", "01", "Test Name");
target.RecordingStopped += (s, args) =>
{
success = (target.CurrentRecording.Duration.TotalSeconds > 4);
_eventRaised.Set();
};
Thread.Sleep(4000);
target.StopRecording();
_eventRaised.WaitOne();
Assert.IsTrue(success);
And if so, can you help me with this test? I need some enlightenment.
I've used the ManualResetEvent many times to test events on other classes and it's worked, but something is different here.

You'll never get an event because the default constructor of WaveIn uses windowed callbacks, and you are not running your unit test on a GUI thread. You should use WaveInEvent instead to work on a non-gui thread. In the very latest NAudio code an InvalidOperationException should be thrown to prevent you from making this mistake.

It is possible that the event is fired before you have connected to it if the _input member is working in its own thread. Hence, the manual reset event will never get set, causing it to wait forever/lock up your unit test.
So perhaps try re-order your definition to:
target.RecordingStopped += (s, args) =>
{
success = (target.CurrentRecording.Duration.TotalSeconds > 4);
_eventRaised.Set();
};
target.StartRecording("1", "01", "Test Name");

Related

event properties change does not trigger

So i have created an event, whenever the property ActualVoltage changed, it will update in Form1 but it doesnt. Property ActualVoltage change, when i send a set-voltage command to the machine, then it will send back a number and i assign that number to AcutalVoltage. pls help me, pls show me where is my mistake and explain it for me like i am a 5 years old kid.Here is my event code:
public delegate void ValueChange();
public event ValueChange Change;
public double ActualVoltage
{
get { return actualVoltage; }
set
{
if (actualVoltage == value) return;
else
{
actualVoltage = value;
OnValueChange();
}
}
}
private void OnValueChange()
{
Change?.Invoke();
}
in Form1:
private void Form1_Load(object sender, EventArgs e)
{
ps.Change += ps_change;
}
private void ps_change()
{
lblValueActualVoltage.Text = ps.ActualVoltage.ToString();
lblValueActualCurrent.Text = ps.ActualCurrent.ToString();
lblHexCur.Text = ps.HexActualCurrent1;
lblHexVol.Text = ps.HexActualVoltage1;
}
updated: in class PS2000B
public void GetDeviceStatusInformation(byte[] rawData)
{
remoteMode = ((byte)(rawData[0] & 0b1)).ToString();
outputMode = ((byte)(rawData[1] & 0b1)).ToString();
List<byte> temp = new List<byte>();
foreach (var v in rawData)
temp.Add(v);
byte[] vontageBytes = temp.GetRange(2, 2).ToArray();
HexActualVoltage = BitConverter.ToString(vontageBytes);
Array.Reverse(vontageBytes);
byte[] currentBytes = temp.GetRange(4, 2).ToArray();
HexActualCurrent = BitConverter.ToString(currentBytes);
Array.Reverse(currentBytes);
var a = (BitConverter.ToInt16(vontageBytes, 0));
ActualVoltage =Math.Round( BitConverter.ToInt16(vontageBytes, 0) * nominalVoltage / 25600.0,2);
ActualCurrent = BitConverter.ToInt16(currentBytes, 0) * nominalCurrent / 25600.0;
}
public void RunTest(string safeFileName,string save)
{
Stopwatch st = new Stopwatch();
List<string> timeMeasure = new List<string>();
List<string> CurrentResults = new List<string>();
List<int> Time = new List<int>();
List<string> Voltage = new List<string>();
FileStream file = new FileStream(safeFileName, FileMode.Open, FileAccess.Read);
StreamReader reader = new StreamReader(file);
string strRead = reader.ReadLine();
while (strRead != null)
{
string[] temp = strRead.Split(';');
Voltage.Add(temp[0]);
Time.Add(int.Parse(temp[1]));
strRead = reader.ReadLine();
}
reader.Close();
file.Close();
int n = 0;
st.Start();
for (int i = 0; i < Voltage.Count(); i++)
{
SetVoltage(Voltage[i]);
for (int j = 0; j < Time[i]/300; j++)
{
UpdateStatusInfomationAndActualValue();
CurrentResults.Add(Voltage[i]+";"+0.3*n+";"+ActualCurrent.ToString()+";"+ HexActualCurrent);
n++;
}
}
st.Stop();
FileStream wfile = new FileStream(save +"\\results.txt", FileMode.Create, FileAccess.Write);
StreamWriter writer = new StreamWriter(wfile);
writer.WriteLine("VOlTAGE;TIME;CURRENT");
foreach (var v in CurrentResults)
writer.WriteLine(v);
writer.WriteLine("TOTAL TIME: "+st.Elapsed);
writer.Close();
wfile.Close();
}
public void SetVoltage(string vol)
{
vol = vol.Replace('.', ',');
ToPowerSupply ToPowerSupply = new ToPowerSupply();
var b = Convert.ToInt16(Single.Parse(vol) * 25600 / nominalVoltage);
var input = BitConverter.GetBytes(b);
Array.Reverse(input);
var temp = ToPowerSupply.SendCommand(0b11, ObjectList.SET_U, input, 2);
ComPort.Write(temp, 0, temp.Count());
Thread.Sleep(150);
int bytes = ComPort.BytesToRead;
byte[] rawData = new byte[bytes];
ComPort.Read(rawData, 0, bytes);
}
Following Form1.cs handles events properly:
public delegate void ValueChange();
public event ValueChange Change;
private double actualVoltage;
public double ActualVoltage
{
get { return actualVoltage; }
set
{
if (actualVoltage == value) return;
else
{
actualVoltage = value;
OnValueChange();
}
}
}
private void ps_change()
{
//UI updates here
}
private void OnValueChange()
{
Change?.Invoke();
}
private void Form1_Load(object sender, EventArgs e)
{
Change += ps_change;
}
Please note: If your property setters are invoked in non-GUI-thread, then you should marshal GUI-updating code to UI thread as mentioned here
If property setter is invoked on a non-GUI thread, the ps_change method should be as follows:
private void ps_change()
{
if (InvokeRequired)
{
BeginInvoke(new Action(ps_change));
return;
}
lblValueActualVoltage.Text = ps.ActualVoltage.ToString();
lblValueActualCurrent.Text = ps.ActualCurrent.ToString();
lblHexCur.Text = ps.HexActualCurrent1;
lblHexVol.Text = ps.HexActualVoltage1;
}
Why?
i call the properties setter not on the GUI thread.
Controls on the form cannot be manipulated from a non-GUI thread. This is because Windows Forms controls are bound to Windows handles, and Windows handles are owned by threads. If a control (Form or Label in this case) is created on one thread (most likely the main application thread), operations on its handle (e.g. changing the text) cannot be performed on a different thread.
To resolve this, the Control class has methods InvokeRequired, Invoke, and BeginInvoke:
InvokeRequired indicates whether the control belongs to a different thread (true) or to the current thread (false).
When InvokeRequired is true, either Invoke or BeginInvoke must be used. Both these methods marshal another delegate to the GUI thread that owns the control. All control manipulations must be performed from that delegate.
The difference between the two methods is that Invoke blocks the calling thread until the delegate is executed on the GUI thread, whereas BeginInvoke just submits invocation to the queue and returns immediately. The marshaling is performed by a special Windows message sent to a window message queue.
More info here: https://msdn.microsoft.com/en-us/library/system.windows.forms.control.invokerequired(v=vs.110).aspx
The usual pattern of implementing this is check InvokeRequired inside an event handler, and if it's true, call either Invoke or BeginInvoke and supply delegate to this same event handler again. The handler is then re-invoked on the GUI thread, where InvokeRequired is false, and then the code safely manipulates form controls.
I finally figure out the reason. I use Thread.Sleep() on main thread. At the time ActualVoltage changes, the main thread is sleeping, thats why the GUI does not update. To handle this, i use BackGroudWorker and everything is fine now.

understanding threads why count not increase

I don't unrdersntat why with this code
static void Main(string[] args)
{
Console.WriteLine("START PROGRAMN-----------------------------------");
test();
Console.WriteLine("END PROGRAMN-----------------------------------");
Console.Read();
}
[ThreadStatic]
private static int i;
private static void test()
{
for (i = 0; i < 2; i++)
{
var bw = new BackgroundWorker();
// define the event handlers
bw.DoWork += (sender, args) =>
{
Console.WriteLine("START Thread-------------");
Console.WriteLine("Print:" + i);
};
bw.RunWorkerCompleted += (sender, args) =>
{
Console.WriteLine("END Thread-------------");
if (args.Error != null)
{
Console.WriteLine(args.Error.ToString());
}
};
bw.RunWorkerAsync(); // starts the
}
}
It will show this in console:
START PROGRAMN-----------------------------------
END PROGRAMN-----------------------------------
START Thread-------------
Print:0
END Thread-------------
START Thread-------------
Print:0
END Thread-------------
Why second print doesn't show print 1?
I think the first iteration is correct because I see print: 0 but in second why I don't see print: 1?
EDIT FOR ANSER
Without [ThreadStatic]
static void Main(string[] args)
{
Console.WriteLine("START PROGRAMN-----------------------------------");
test();
Console.WriteLine("END PROGRAMN-----------------------------------");
Console.Read();
}
[ThreadStatic]
private static int i;
private static void test()
{
for (i = 0; i < 2; i++)
{
var bw = new BackgroundWorker();
// define the event handlers
bw.DoWork += (sender, args) =>
{
Console.WriteLine("START Thread-------------");
Console.WriteLine("Print:" + i);
};
bw.RunWorkerCompleted += (sender, args) =>
{
Console.WriteLine("END Thread-------------");
if (args.Error != null)
{
Console.WriteLine(args.Error.ToString());
}
};
bw.RunWorkerAsync(); // starts the
}
}
It will show this in console:
START PROGRAMN-----------------------------------
END PROGRAMN-----------------------------------
START Thread-------------
Print:2
END Thread-------------
START Thread-------------
Print:2
END Thread-------------
Why first pirnt doesn't show print 0 and second print 1?
why show 2?
I think you do not really understand the ThreadStatic attribute here. It means that, by definition, Indicates that the value of a static field is unique for every thread. That means the value is unique for the Main Thread (where you are creating the BackgroundWorkers) and the BackgroundWorkers, that will always have the default value 0 for i.
Forget about that ThreadStatic you are not using this properly. It's not what you need in you case. The problem you are trying to bypass is because otherwise your result gets the latest value because the DoEvent is not started on the first thread that it already changed. You end up with a race condition. You do need to use arguments to have clear local instance of the variable. the easiest way is to change your code like so
for (int i = 0; i < 2; i++)
{
var bw = new BackgroundWorker();
// define the event handlers
bw.DoWork += (sender, args) =>
{
// get the argument
var value = args.Argument.ToString();
Console.WriteLine("START Thread-------------");
Console.WriteLine("Print:" + value);
};
bw.RunWorkerCompleted += (sender, args) =>
{
Console.WriteLine("END Thread-------------");
if (args.Error != null)
{
Console.WriteLine(args.Error.ToString());
}
};
bw.RunWorkerAsync(i); // starts the thread with arguments
}
Check your 'Console.WriteLine("Print:" + i);' inside the lambda.
The "Print:" + i is not evaluated and concatenated while inside the for loop. It will be present in your anonymous method and only will be concatenated when that method runs.
Because of your [ThreadStatic] attribute, int i is not shared between threads. "Each executing thread has a separate instance of the field, and independently sets and gets values for that field. If the field is accessed on a different thread, it will contain a different value."
int i will be instantiated and you get the int default value.

Memory leak in anonymous function

TLDR;
Non-trivial memory leak, can be seen easily in Resharper. See minimal example below.
I'm seeing a memory leak in the following program but failing to see why.
The program sends pings to a number of hosts asynchronously and determines if at least one is ok. To do that, a method (SendPing()) that runs these async operations is repeatedly called which runs them in a background thread (it doesn't have to, but in the actual application SendPing() will be called by the main UI thread which shouldn't be blocked).
The task seems pretty trivial but I think the leak occurs due to the way I create lambdas inside the SendPing() method. The program can be changed to not use lambdas but I'm more interested in understanding what causes the leak here.
public class Program
{
static string[] hosts = { "www.google.com", "www.facebook.com" };
static void SendPing()
{
int numSucceeded = 0;
ManualResetEvent alldone = new ManualResetEvent(false);
ManualResetEvent[] handles = new ManualResetEvent[hosts.Length];
for (int i = 0; i < hosts.Length; i++)
handles[i] = new ManualResetEvent(false);
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (sender, args) =>
{
numSucceeded = 0;
Action<int, bool> onComplete = (hostIdx, succeeded) =>
{
if (succeeded) Interlocked.Increment(ref numSucceeded);
handles[hostIdx].Set();
};
for (int i = 0; i < hosts.Length; i++)
SendPing(i, onComplete);
ManualResetEvent.WaitAll(handles);
};
worker.RunWorkerCompleted += (sender, args) =>
{
Console.WriteLine("Succeeded " + numSucceeded);
BackgroundWorker bgw = sender as BackgroundWorker;
alldone.Set();
};
worker.RunWorkerAsync();
alldone.WaitOne();
worker.Dispose();
}
static void SendPing(int hostIdx, Action<int, bool> onComplete)
{
Ping pingSender = new Ping();
pingSender.PingCompleted += (sender, args) =>
{
bool succeeded = args.Error == null && !args.Cancelled && args.Reply != null && args.Reply.Status == IPStatus.Success;
onComplete(hostIdx, succeeded);
Ping p = sender as Ping;
p.Dispose();
};
string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
byte[] buffer = Encoding.ASCII.GetBytes(data);
PingOptions options = new PingOptions(64, true);
pingSender.SendAsync(hosts[hostIdx], 2000, buffer, options, hostIdx);
}
private static void Main(string[] args)
{
for (int i = 0; i < 1000; i++)
{
Console.WriteLine("Send ping " + i);
SendPing();
}
}
}
Resharper shows the leaks are due to uncollected closure objects (c__DisplayClass...).
From what I understand, there shouldn't be a leak because there are no circular references (as far as I see) and therefore GC should take of the leaks. I also call Dispose to release the thread (bgw) + sockets (Ping object) promptly. (Even if I didn't GC will clean them up eventually, won't it?)
Suggested changes from comments
Remove event handles before Disposing
Dispose ManualResetEvent
But the leak is still there!
Changed program:
public class Program
{
static string[] hosts = { "www.google.com", "www.facebook.com" };
static void SendPing()
{
int numSucceeded = 0;
ManualResetEvent alldone = new ManualResetEvent(false);
BackgroundWorker worker = new BackgroundWorker();
DoWorkEventHandler doWork = (sender, args) =>
{
ManualResetEvent[] handles = new ManualResetEvent[hosts.Length];
for (int i = 0; i < hosts.Length; i++)
handles[i] = new ManualResetEvent(false);
numSucceeded = 0;
Action<int, bool> onComplete = (hostIdx, succeeded) =>
{
if (succeeded) Interlocked.Increment(ref numSucceeded);
handles[hostIdx].Set();
};
for (int i = 0; i < hosts.Length; i++)
SendPing(i, onComplete);
ManualResetEvent.WaitAll(handles);
foreach (var handle in handles)
handle.Close();
};
RunWorkerCompletedEventHandler completed = (sender, args) =>
{
Console.WriteLine("Succeeded " + numSucceeded);
BackgroundWorker bgw = sender as BackgroundWorker;
alldone.Set();
};
worker.DoWork += doWork;
worker.RunWorkerCompleted += completed;
worker.RunWorkerAsync();
alldone.WaitOne();
worker.DoWork -= doWork;
worker.RunWorkerCompleted -= completed;
worker.Dispose();
}
static void SendPing(int hostIdx, Action<int, bool> onComplete)
{
Ping pingSender = new Ping();
PingCompletedEventHandler completed = null;
completed = (sender, args) =>
{
bool succeeded = args.Error == null && !args.Cancelled && args.Reply != null && args.Reply.Status == IPStatus.Success;
onComplete(hostIdx, succeeded);
Ping p = sender as Ping;
p.PingCompleted -= completed;
p.Dispose();
};
pingSender.PingCompleted += completed;
string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
byte[] buffer = Encoding.ASCII.GetBytes(data);
PingOptions options = new PingOptions(64, true);
pingSender.SendAsync(hosts[hostIdx], 2000, buffer, options, hostIdx);
}
private static void Main(string[] args)
{
for (int i = 0; i < 1000; i++)
{
Console.WriteLine("Send ping " + i);
SendPing();
}
}
}
There is no memory leak. dotMemory that you use analyzes the snapshots and indeed, in the context of one snapshot the auto-generated class created by the compiler for the completed event handler will still be in memory. Rewrite your main application like this:
private static void Main(string[] args)
{
for (int i = 0; i < 200; i++)
{
Console.WriteLine("Send ping " + i);
SendPing();
}
Console.WriteLine("All done");
Console.ReadLine();
}
Run the profiler, allow the application to reach the point where it outputs "All done", wait a few seconds and take a new snapshot. You will see there is no longer any memory leak.
It is worth mentioning that the class generated by the compiler for the PingCompleted event handler (that is c_DisplayClass6) will linger in memory after the method static void SendPing(int hostIdx, Action<int, bool> onComplete) exits. What happens there is that when pingSender.PingCompleted += (sender, args) =>... is executed the pingSender instance will take a reference to c_DisplayClass6. During the call to pingSender.SendAsync, the framework will retain a reference to pingSender in order to deal with running the async method and its completion. The async method you initiate by calling pingSender.SendAsync still runs when method SendPing exits. Because of that pingSender will survive a little while longer, hence c_DisplayClass6 will survive a little while longer too. However, after the pingSender.SendAsync operation completes, the framework will release its references to pingSender. At this point both pingSender and c_DisplayClass6 become garbage collectable and eventually the garbage collector will collect them. You can see this if you take a last snapshot like I was mentioning above. In that snapshot dotMemory will no longer detect a leak.
ManualResetEvent implements Dispose(). You are instantiating a number of ManualResetEvents and never calling dispose.
When an object implements dispose you need to call it. If you do not call it, there'll quite likely be memory leaks. You should use using statements, and try finally to dispose objects Simarly you should also have a using statement around Ping.
EDIT: This may be useful....
When should a ManualResetEvent be disposed?
EDIT: As stated here...
https://msdn.microsoft.com/en-us/library/498928w2(v=vs.110).aspx
When you create objects that include unmanaged resources, you must
explicitly release those resources when you finish using them in your
app.
EDIT: As stated here...
https://msdn.microsoft.com/en-us/library/system.threading.manualresetevent(v=vs.100).aspx
Dispose() Releases all resources used by the current instance of the
WaitHandle class. (Inherited from WaitHandle.)
The ManualResetEvent has unmanaged resources associated with it, which is fairly typical of most of the classes in the .NET Framework libraries which implements IDisposable.
EDIT: Try using this...
public class Program
{
static string[] hosts = { "www.google.com", "www.facebook.com" };
static void SendPing()
{
int numSucceeded = 0;
using (ManualResetEvent alldone = new ManualResetEvent(false))
{
BackgroundWorker worker = null;
ManualResetEvent[] handles = null;
try
{
worker = new BackgroundWorker();
DoWorkEventHandler doWork = (sender, args) =>
{
handles = new ManualResetEvent[hosts.Length];
for (int i = 0; i < hosts.Length; i++)
handles[i] = new ManualResetEvent(false);
numSucceeded = 0;
Action<int, bool> onComplete = (hostIdx, succeeded) =>
{
if (succeeded) Interlocked.Increment(ref numSucceeded);
handles[hostIdx].Set();
};
for (int i = 0; i < hosts.Length; i++)
SendPing(i, onComplete);
ManualResetEvent.WaitAll(handles);
foreach (var handle in handles)
handle.Close();
};
RunWorkerCompletedEventHandler completed = (sender, args) =>
{
Console.WriteLine("Succeeded " + numSucceeded);
BackgroundWorker bgw = sender as BackgroundWorker;
alldone.Set();
};
worker.DoWork += doWork;
worker.RunWorkerCompleted += completed;
worker.RunWorkerAsync();
alldone.WaitOne();
worker.DoWork -= doWork;
worker.RunWorkerCompleted -= completed;
}
finally
{
if (handles != null)
{
foreach (var handle in handles)
handle.Dispose();
}
if (worker != null)
worker.Dispose();
}
}
}
static void SendPing(int hostIdx, Action<int, bool> onComplete)
{
using (Ping pingSender = new Ping())
{
PingCompletedEventHandler completed = null;
completed = (sender, args) =>
{
bool succeeded = args.Error == null && !args.Cancelled && args.Reply != null && args.Reply.Status == IPStatus.Success;
onComplete(hostIdx, succeeded);
Ping p = sender as Ping;
p.PingCompleted -= completed;
p.Dispose();
};
pingSender.PingCompleted += completed;
string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
byte[] buffer = Encoding.ASCII.GetBytes(data);
PingOptions options = new PingOptions(64, true);
pingSender.SendAsync(hosts[hostIdx], 2000, buffer, options, hostIdx);
}
}
private static void Main(string[] args)
{
for (int i = 0; i < 1000; i++)
{
Console.WriteLine("Send ping " + i);
SendPing();
}
}
}

AutoResetEvent in Windows Phone project - can't invoke handler

It's from Windows Phone project. I am trying to invoke few handlers, handler by handler to receive information about GPS / Reverse position. I wonder why it won't run correctly.
When I setup only 1 coordinate it's ok. I have message with Street etc. But when there is more coordinates my handler isn't invoke.
private async void SimulationResults()
{
done = new AutoResetEvent(true);
Geolocator geolocator = new Geolocator();
geolocator.DesiredAccuracy = PositionAccuracy.High;
myCoordinate = new GeoCoordinate(51.751985, 19.426515);
if (myMap.Layers.Count() > 0) myMap.Layers.Clear();
mySimulation = new List<SimulationItem>();
mySimulation = Simulation.SimulationProcess(myCoordinate, 120); // Odległość
for(int i = 0; i<2; i++)
{
done.WaitOne();
if (mySimulation.ElementAt(i).Id == 1 | mySimulation.ElementAt(i).Id == -1)
{
// Oczekiwanie, ponieważ obiekt jest zasygnalizowany od razu wejdziemy
// do sekcji krytycznej
AddMapLayer(mySimulation.ElementAt(i).Coordinate, Colors.Yellow, false);
myReverseGeocodeQuery_1 = new ReverseGeocodeQuery();
myReverseGeocodeQuery_1.GeoCoordinate = mySimulation.ElementAt(i).Coordinate;
myReverseGeocodeQuery_1.QueryCompleted += ReverseGeocodeQuery_QueryCompleted_1;
// Sekcja krytyczna
done.Reset(); // Hey I'm working, wait!
myReverseGeocodeQuery_1.QueryAsync();
}
}
MessageBox.Show("Skonczylem");
}
private void ReverseGeocodeQuery_QueryCompleted_1(object sender, QueryCompletedEventArgs<IList<MapLocation>> e)
{
done.Set();
if (e.Error == null)
{
if (e.Result.Count > 0)
{
MapAddress address = e.Result[0].Information.Address;
MessageBox.Show("Wykonano "+address.Street);
}
}
}
What's happening here is that you are blocking on your AutoResetEvent on the UI thread, but that's the same thread that the ReverseGeocodeQuery is trying to run on. Since it's blocked it can't run and it also can't invoke your callback.
A very quick fix that doesn't change your flow too much and assumes some sort of "on everything complete do X" requirement is below. I triggered the whole thing on a background thread with:
new Thread(new ThreadStart(() =>
{
SimulationResults();
})).Start();
Since all of the below is on a background thread I needed to add some Dispatcher.BeginInvoke() calls around anything that called into the UI thread, but this way the thread that is blocked is your background thread and not your UI thread.
AutoResetEvent done;
int remaining;
private async void SimulationResults()
{
done = new AutoResetEvent(true);
Geolocator geolocator = new Geolocator();
geolocator.DesiredAccuracy = PositionAccuracy.High;
var myCoordinate = new GeoCoordinate(51.751985, 19.426515);
var mySimulation = new List<GeoCoordinate>()
{
new GeoCoordinate(51.751985, 19.426515),
new GeoCoordinate(2, 2)
};
//mySimulation = Simulation.SimulationProcess(myCoordinate, 120); // Odległość
remaining = mySimulation.Count;
for (int i = 0; i < mySimulation.Count; i++)
{
done.WaitOne();
//if (mySimulation.ElementAt(i).Id == 1 | mySimulation.ElementAt(i).Id == -1)
//{
// Oczekiwanie, ponieważ obiekt jest zasygnalizowany od razu wejdziemy
// do sekcji krytycznej
//AddMapLayer(mySimulation.ElementAt(i).Coordinate, Colors.Yellow, false);
var tempI = i;
Dispatcher.BeginInvoke(() =>
{
var myReverseGeocodeQuery_1 = new ReverseGeocodeQuery();
myReverseGeocodeQuery_1.GeoCoordinate = mySimulation.ElementAt(tempI);
myReverseGeocodeQuery_1.QueryCompleted += ReverseGeocodeQuery_QueryCompleted_1;
// Sekcja krytyczna
done.Reset(); // Hey I'm working, wait!
myReverseGeocodeQuery_1.QueryAsync();
});
//}
}
}
private void ReverseGeocodeQuery_QueryCompleted_1(object sender, QueryCompletedEventArgs<IList<MapLocation>> e)
{
done.Set();
remaining--;
if (e.Error == null)
{
if (e.Result.Count > 0)
{
MapAddress address = e.Result[0].Information.Address;
Dispatcher.BeginInvoke(() =>
{
MessageBox.Show("Wykonano " + address.Street);
});
}
}
if (remaining == 0)
{
// Do all done code
Dispatcher.BeginInvoke(() =>
{
MessageBox.Show("Skonczylem");
});
}
}
Alternatively you could also make this a proper async method that awaits on different events to progress.

Asynchronous event in c#

In my application I have three asynchronous events.
After all of them are complete I need to call some Method1().
How can I implement this logic?
Update
Here is one of my asynchronous events:
public static void SetBackground(string moduleName, Grid LayoutRoot)
{
var feedsModule = FeedHandler.GetInstance().ModulesSetting.Where(type => type.ModuleType == moduleName).FirstOrDefault();
if (feedsModule != null)
{
var imageResources = feedsModule.getResources().getImageResource("Background") ??
FeedHandler.GetInstance().MainApp.getResources().getImageResource("Background");
if (imageResources != null)
{
//DownLoad Image
Action<BitmapImage> onDownloaded = bi => LayoutRoot.Background = new ImageBrush() { ImageSource = bi, Stretch = Stretch.Fill };
CacheImageFile.GetInstance().DownloadImageFromWeb(new Uri(imageResources.getValue()), onDownloaded);
}
}
}
Bit field (or 3 booleans) set by each event handler. Each event handler checks that the condition is met then calls Method1()
tryMethod1()
{
if (calledEvent1 && calledEvent2 && calledEvent3) {
Method1();
calledEvent1 = false;
calledEvent2 = false;
calledEvent3 = false;
}
}
eventHandler1() {
calledEvent1 = true;
// do stuff
tryMethod1();
}
Not given any other information, what will work is to use a counter. Just an int variable that is initialized to be 3, decremented in all handlers and checked for equality to 0 and that case go on.
You should use WaitHandles for this. Here is a quick example, but it should give you the basic idea:
List<ManualResetEvent> waitList = new List<ManualResetEvent>() { new ManualResetEvent(false), new ManualResetEvent(false) };
void asyncfunc1()
{
//do work
waitList[0].Set();
}
void asyncfunc2()
{
//do work
waitList[1].Set();
}
void waitFunc()
{
//in non-phone apps you would wait like this:
//WaitHandle.WaitAll(waitList.ToArray());
//but on the phone 'Waitall' doesn't exist so you have to write your own:
MyWaitAll(waitList.ToArray());
}
void MyWaitAll(WaitHandle[] waitHandleArray)
{
foreach (WaitHandle wh in waitHandleArray)
{
wh.WaitOne();
}
}

Categories

Resources