How to synchronize the container application through events |
|
Some method available inside Audio Sound Recorder for .NET performs lengthy operations which, on a single-threaded environment, could block the user interface of the container application also for several seconds: just think about the time requested to load a sound file whose duration is more than 30 minutes or to perform the waveform analysis for a song longer than 5 minutes and you will perfectly understand that this kind of tasks couldn't be completed in less than one second.
In order to avoid this kind of blocks, the control performs lengthy operations inside secondary threads and you have the choice to decide if the threading model will work in asynchronous mode or in synchronous mode. Let's see the difference between these two modes:
For historical reasons and in order to avoid breaking compatibility with past versions of the component, this is NOT the default mode so, in order to use the synchronous mode, you will have to enable it using the UseThreadsInSyncMode method with its bUseThreadsInSyncMode parameter set to true.
When using the synchronous mode, loading a sound file and immediately performing a waveform analysis may work like this (Visual C# code):
// set the synch mode audioSoundRecorder1.UseThreadsInSyncMode (true);
// load a sound file and check the result enumErrorCodes nReturn = audioSoundRecorder1.StartFromFile ("", @"c:\myfile.mp3"); if (nReturn != enumErrorCodes.ERR_NOERROR) { MessageBox.Show ("An error occurred"); return; }
// perform waveform analysis audioSoundRecorder1.DisplayWaveformAnalyzer.AnalyzeFullSound ();
// display the waveform on the analyzer audioSoundRecorder1.DisplayWaveformAnalyzer.SetDisplayRange (0, -1);
|
In this case the waveform analysis would not start until the StartFromFile method doesn't return from its execution so, when calling the DisplayWaveformAnalyzer.AnalyzeFullSound method, you would be sure that the loaded sound is totally in memory and can be analyzed without problems; at the same time, the displaying of the waveform through the WaveformAnalyzer.SetDisplayRange method will not be executed until the waveform analysis, started through the DisplayWaveformAnalyzer.AnalyzeFullSound method, is not completed.
It's important to note that in this situation the container application's user interface will not be blocked and that the container application will continue receiving events generated by the component itself or by other elements of the user interface: this allows for example to update the value of a progress bar control during the loading of the sound file or during the waveform analysis and to cancel the sound loading by clicking a button whose event handler will call the Stop method.
As a final note, it's very important to remember that a call to a method of a certain .NET component should be never performed from within a management function of an event generated by the same .NET component: this is usually cause of errors and dead-lock situations and it's a practice that should be always avoided.
This is the default mode adopted by the component since its first release: the main side effect of this multithreaded approach is the fact that, when the method call returns, the requested operation could still be running in background so requested data wouldn't be already available and any method call trying to access data would fail by returning an error code; this means that, for example, the code below will not work in most cases:
THE CODE BELOW WILL NOT WORK
enumErrorCodes nReturn = audioSoundRecorder1.StartFromFile ("", @"c:\myfile.mp3"); audioSoundRecorder1.DisplayWaveformAnalyzer.AnalyzeFullSound ();
|
The reason why, in most cases, code above may not work is quite simple: when the WaveformAnalyzer.AnalyzeFullSound method is called, the secondary thread started by StartFromFile method is still running in background so the waveform analysis doesn't have full sound data available; as a further note, the nReturn value only reports us if the launch of the secondary thread was performed without errors but will not tell us if the loading of the sound file was completed successfully; for this reason it's very important that the container application synchronizes itself with events fired by the control.
A list of methods that will start a secondary thread can be find on the table below:
RecordedSound.RequestDeleteRange RecordedSound.RequestExportToFile RecordedSound.RequestInsertSilence RecordedSound.RequestReduceToRange |
Let's make a couple of practical examples for having a better understanding of the issue:
• | Loading a sound file |
The container application requests the loading of a sound file through a certain number of methods, for example StartFromFile, StartFromFileEncrypted, StartFromMemory, etc: the call to one of these methods will start a secondary thread and will immediately return to the caller, meaning that the loading session will be still working in background when the method call will be completed;
The container application needs to synchronize its code through events generated by the component so loading advancement could be monitored through following events:
• | RecordingStarted : this event is generated immediately before starting the secondary thread; the container application could catch it in order to display a hidden progress bar that could be used to notify the user about the loading session advancement. |
• | RecordingPerc : this event is generated during the loading session; the container application could catch it in order to modify the mentioned progress bar value. |
• | RecordingStopped : this event is generated immediately before closing the secondary thread; the container application could catch it in order to know if the file was loaded successfully and to hide the progress bar. |
After receiving the last event, the container application could request further operations with the loaded sound, for example it could start an editing session or to export the loaded sound in a different sound format.
It's very important to remember that a call to a method of a certain .NET component should be never performed from within a management function of an event generated by the same .NET component: this is usually cause of errors and dead-lock situations and it's a practice that should be always avoided; the best approach in cases like this would be using the RecordingStopped event to trigger a one-shot timer and to call methods for editing or exporting the sound file from within the management function of the timer's event.
• | Editing a sound file |
The container application requests the editing of a sound file through a certain number of methods, for example through methods available inside the RecordedSound class having the "Apply" suffix, for example RecordedSound.RequestDeleteRange, RecordedSound.RequestInsertSilence, RecordedSound.RequestReduceToRange, etc.: the call to one of these methods will start a secondary thread and will immediately return to the caller, meaning that the editing session will be still working in background when the method call will be completed; the container application will be notified about editing advancement through the following events:
• | SoundEditStarted : this event is generated immediately before starting the secondary thread; the container application could catch it in order to display a string on the user interface informing.that the editing session in running. |
• | SoundEditDone : this event is generated immediately before closing the secondary thread. |
After receiving the last event, the container application could request further operations with the edited sound, for example it could start an exporting session of the loaded sound in a different sound format.
• | Performing waveform's analysis of the loaded sound file |
The container application requests the calculation of the sound's waveform through a call to the WaveformAnalyzer.AnalyzeFullSound method; when this method returns, the waveform is still being calculated in background and the container application will be notified about calculation advancement through the following events:
• | WaveAnalysisStart : this event is generated immediately before starting the secondary calculation thread; the container application could catch it in order to display a hidden progress bar that could be used to notify the user about the analysis advancement. |
• | WaveAnalysisPerc : this event is generated during the calculation; the container application could catch it in order to modify the mentioned progress bar value |
• | WaveAnalysisDone : this event is generated immediately before closing the secondary thread; the container application could catch it in order to hide the progress bar and, eventually, to display a message to the user |
After receiving the last event, the container application could request further operations with the calculated waveform, for example it could request to obtain the bitmap representation of the waveform through a call to the WaveformAnalyzer.SnapshotViewSaveToFile method.
Also in this case it's very important to remember that a call to a method of a certain .NET component should be never performed from within a management function of an event generated by the same .NET component: this is usually cause of errors and dead-lock situations and it's a practice that should be always avoided; the best approach in cases like this would be using the WaveAnalysisDone event to trigger a one-shot timer and to call the WaveformAnalyzer.SnapshotViewSaveToFile method from within the management function of the timer's event.
As a latest suggestion, keep management functions of events generated by the component as fast/short as possible and never use them in order to display messages or dialog boxes which require user interaction.
The procedure below allows using a single one-shot timer that could manage some of the situations described above:
• | At design-time, from the Visual Studio toolbox, insert a Timer object inside the form containing the Audio Sound Recorder component: inside this sample it will be named TimerSync |
• | At design-time, set the Enabled property of the timer object to False |
• | At design-time, set the Interval property of the timer object to 50 |
• | Inside your code add a global variable of type "String" that you could use for knowing the next operation that needs to be performed immediately after receiving the timer's tick: inside this sample it will be named g_strNextOperation |
Supposing that we have requested a loading session of a sound file through the StartFromFile method, catch the RecordingStopped event and add the code below:
private void audioSoundRecorder1_RecordingStopped(object sender, AudioSoundRecorder.RecordingStoppedEventArgs e) { if (e.bResult == true) { // force analysis of the loaded sound g_strNextOperation = "AnalyzeSound"; TimerSync.Enabled = true; } else MessageBox.Show ("Sound failed to load with the following error code: " + audioSoundRecorder1.LastError.ToString ()); }
|
Now let's catch the timer's tick and perform requested operation
private void TimerSync_Tick(object sender, System.EventArgs e) { // reset the timer to avoid unwanted recursions TimerSync.Enabled = false;
// start requested operation if (g_strNextOperation == "AnalyzeSound") audioSoundRecorder1.DisplayWaveformAnalyzer.AnalyzeFullSound (); }
|
The code above starts the needed waveform analysis so, in order to know when this will be completed, we need to catch the WaveAnalysisDone event like this:
private void audioSoundRecorder1_WaveAnalysisDone(object sender, AudioSoundRecorder.WaveAnalysisDoneEventArgs e) { // force bitmap creation g_strNextOperation = "CreateWaveformBitmap"; TimerSync.Enabled = true; }
|
At this point we can modify the handler of the TimerSync timer in order to manage the bitmap creation feature:
private void TimerSync_Tick(object sender, System.EventArgs e) { // reset the timer to avoid unwanted recursions TimerSync.Enabled = false;
// start requested operation switch (g_strNextOperation) { case "AnalyzeSound": audioSoundRecorder1.DisplayWaveformAnalyzer.AnalyzeFullSound (); break; case "CreateWaveformBitmap": audioSoundRecorder1.DisplayWaveformAnalyzer.SnapshotViewSaveToFile (0, -1, etc.); break; } }
|
As you may understand, you could expand the timer's handler above for all operations executed inside secondary threads by adding new values for the g_strNextOperation variable.