Before I start, the closest I could find for this here was How do you implement an async action delegate method? however, being a bear of a somewhat small brain, I couldn't get my braincells around the answer and how it might apply to my current issue.
I'm writing a wrapper around the RabbitMq C# Client, and am having a problem with my delegates and the async events.
This bit of code seems fine:
private Action<object, BasicAckEventArgs> _syncHandlerBasicAck;
private Action<object, BasicNackEventArgs> _syncHandlerBasicNack;
Defining the code here:
_syncHandlerBasicAck = delegate(object o, BasicAckEventArgs args)
{
//[Do stuff here]
};
_syncHandlerBasicNack = delegate(object o, BasicNackEventArgs args)
{
//[Do stuff here]
};
then using it with:
Channel.BasicAcks += _syncHandlerBasicAck.Invoke;
Channel.BasicNacks += _syncHandlerBasicNack.Invoke;
The issue I have is with the async BasicDeliver event. This is my current code:
private Func<object, BasicDeliverEventArgs, Task> _syncHandlerIncomingMessage;
followed by
_syncHandlerIncomingMessage = async delegate(object o, BasicDeliverEventArgs args)
{
//[Do stuff here]
};
however whenever I try to do:
Consumer.Received += _syncHandlerIncomingMessage.Invoke;
I get a System.NullReferenceException telling me that
Object reference not set to an instance of an object.
I see that there's a BeginInvoke method available but I'm not sure that applies to my current situation since I'm not using Callbacks and just want to call this asynchronously.
Anyone got any pointers? (I'll even take "Duplicate question: Answer here" type responses as I'm sure I may have missed something).
Edit
I had two issues; one with wiring this up, the other where some refactoring got in my way! Thanks so much to #Coolbots for the answer and #Camilo to show me the error of my ways.
The problem is event subscriptions do not support async delegate directly, so a wrapper is needed, like so:
Alternative syntax (see EDIT):
async Task MyDelegate(object sender, BasicDeliverEventArgs e)
{
// Do stuff
}
Consumer.Received += async (s, e) => await MyDelegate(s, e);
I'm on my phone (can't test code), so while I'm certain this works with an async Task MyDelegate(...), I'm not 100% this will work with async delegate MyDelegate(...); although, in theory, it should.
EDIT: Deleted, then restored my answer per OP's request (I'm glad it was of some value); finally got to a computer, and verified that all 3 subscription approaches work, as verified by #CamiloTerevinto in the commments, and reason for my original deletion of this answer:
Func<object, BasicDeliverEventArgs, Task> _syncHandlerIncomingMessage;
_syncHandlerIncomingMessage = async delegate (object sender, BasicDeliverEventArgs eventArgs)
{
await Task.Delay(1000); // Do stuff
};
// OR
async Task SyncHandlerIncomingMessage(object sender, BasicDeliverEventArgs eventArgs)
{
await Task.Delay(1000); // Do stuff
}
// Can be subscribed as follows:
Consumer.Received += _syncHandlerIncomingMessage.Invoke; // as posted by OP
// OR
Consumer.Received += async (s, e) => await SyncHanlderIncomingMessage.Invoke(s, e);
// OR
Consumer.Received += SyncHandlerIncomingMessage;
In summary: no wrapper necessary. I am glad my original answer was of value to the OP; I am adding this edit for completeness/correctness of the answer, in hopes that it's of use to others.
Related
I am currently experiencing some unexpected/unwanted behavior with an aync method I am trying to use. The async method is RecognizeAsync. I am unabled to await this method since it returns void. What is happening, is that ProcessAudio method will be called first and will seemingly run to completion however the webpage never returns my "Contact" view as it should or errors out. After the method runs to completion, the breakpoints in my handlers start being hit. If I let it play through to completion, no redirect will ever happen- in the network tab in chrome debugger, the "status" will stay marked as pending and just hang there. I believe my issue is being caused by issues with asynchronousity but have been unable to found out what exactly it is.
All help is appreciated.
[HttpPost]
public async Task<ActionResult> ProcessAudio()
{
SpeechRecognitionEngine speechEngine = new SpeechRecognitionEngine();
speechEngine.SetInputToWaveFile(Server.MapPath("~/Content/AudioAssets/speechSample.wav"));
var grammar = new DictationGrammar();
speechEngine.LoadGrammar(grammar);
speechEngine.SpeechRecognized += new EventHandler<SpeechRecognizedEventArgs>(SpeechRecognizedHandler);
speechEngine.SpeechHypothesized += new EventHandler<SpeechHypothesizedEventArgs>(SpeechHypothesizedHandler);
speechEngine.RecognizeAsync(RecognizeMode.Multiple);
return View("Contact", vm); //first breakpoint hit occurs on this line
//but it doesnt seem to be executed?
}
private void SpeechRecognizedHandler(object sender, EventArgs e)
{
//do some work
//3rd breakpoint is hit here
}
private void SpeechHypothesizedHandler(object sender, EventArgs e)
{
//do some different work
//2nd breakpoint is hit here
}
UPDATE: based on suggestions, I have changed my code to (in ProcessAudio):
using (speechEngine)
{
speechEngine.SetInputToWaveFile(Server.MapPath("~/Content/AudioAssets/speechSample.wav"));
var grammar = new DictationGrammar();
speechEngine.LoadGrammar(grammar);
speechEngine.SpeechRecognized += new EventHandler<SpeechRecognizedEventArgs>(SpeechRecognizedHandler);
speechEngine.SpeechHypothesized += new EventHandler<SpeechHypothesizedEventArgs>(SpeechHypothesizedHandler);
var tcsRecognized = new TaskCompletionSource<EventArgs>();
speechEngine.RecognizeCompleted += (sender, eventArgs) => tcsRecognized.SetResult(eventArgs);
speechEngine.RecognizeAsync(RecognizeMode.Multiple);
try
{
var eventArgsRecognized = await tcsRecognized.Task;
}
catch(Exception e)
{
throw (e);
}
}
and this is resulting in some wrong behavior:
The return View("Contact",vm) breakpoint will now be hit AFTER the handlers are finished firing however there is still no redirect that ever happens. I am never directed to my Contact page. I just si ton my original page indefinitely just like before.
You're going too early. The speech engine probably hasn't even started by the time you hit the return View line.
You need to wait until the final event is fired from the speech engine. The best approach would be to convert from the event based asynchrony to TAP-based asynchrony.
This can be achieved by using TaskCompletionSource<T>
Let's deal with (what I believe) should be the last event to fire after speechEngine.RecognizeAsync is called, i.e. SpeechRecognized. I'm assuming that this is the event that fires when the final result has been calculated by the speech engine.
So, first:
var tcs = new TaskCompletionSource<EventArgs>();
now lets hook it up to complete when SpeechRecognized is fired, using inline lambda-style method declaration:
speechEngine.SpeechRecognized += (sender, eventArgs) => tcs.SetResult(eventArgs);
(...wait... what happens if no speech was recognized? We'll also need to hook up the SpeechRecognitionRejected event and define a custom Exception subclass for this type of event... here I'll just call it RecognitionFailedException. Now we're trapping all possible outcomes of the recognition process, so we would hope that the TaskCompletionSource would complete in all outcomes.)
speechEngine.SpeechRecognitionRejected += (sender, eventArgs) =>
tcs.SetException(new RecognitionFailedException());
then
speechEngine.RecognizeAsync(RecognizeMode.Multiple);
now, we can await the Task property of our TaskCompletionSource:
try
{
var eventArgs = await tcs.Task;
}
catch(RecognitionFailedException ex)
{
//this would signal that nothing was recognized
}
do some processing on the EventArgs that is the Task's result, and return a viable result back to the client.
In the process of doing this, you are creating IDisposable instances that will need to be properly disposed.
So:
using(SpeechRecognitionEngine speechEngine = new SpeechRecognitionEngine())
{
//use the speechEngine with TaskCompletionSource
//wait until it's finished
try
{
var eventArgs = await tcs.Task;
}
catch(RecognitionFailedException ex)
{
//this would signal that nothing was recognized
}
} //dispose
if anyone is curious- i solved my issue by doing the following:
I changed to using Recognize() instead of RecognizeAsync(..) which lead to InvalidOperationException due to async events trying to be executed at an "invalid time in the pages lifecycle". To overcome this, I wrapped my operations in a thread and joined the thread back to the main thread directly after running it. Code below:
using (speechEngine)
{
var t = new Thread(() =>
{
speechEngine.SetInputToWaveFile(#"C:\AudioAssets\speechSample.wav");
speechEngine.LoadGrammar(dictationGrammar);
speechEngine.SpeechRecognized += new EventHandler<SpeechRecognizedEventArgs>(SpeechRecognizedHandler);
speechEngine.SpeechHypothesized += new EventHandler<SpeechHypothesizedEventArgs>(SpeechHypothesizedHandler);
speechEngine.Recognize();
});
t.Start();
t.Join();
}
}
This is a follow-up question to another SO question regarding the use of an async wrapper over an async callback function.
Here is the code as it stands (an excellent solution provided by #Servy):
static Task<ObservableCollection<MyResult>> GetMyDataAsync(Params p)
{
var tcs = new TaskCompletionSource<ObservableCollection<MyResult>>();
DoStuffClass stuff = new DoStuffClass();
stuff.LoadCompleted += (args) => tcs.TrySetResult(args.Result);
stuff.LongDrawOutProcessAsync(p);
return tcs.Task;
}
So, my problem is with the LoadCompleted event; here is the signature:
public event EventHandler<MyArgs> LoadCompleted;
MyArgs contains a property called ResultCollection; however, changing the code like this does not work:
stuff.LoadCompleted += (args) => tcs.TrySetResult(args.ResultCollection);
In fact, I get the error:
'System.EventHandler<MyArgs>' does not take 1 arguments
Which I can see if correct from the signature; so how can I set the LoadCompleted result to the TaskCompletionSource?
EventHandler needs 2 arguments, the first is the instance that raised the event and the second is the event arguments. You need to specify both of them even if you only use one (args).
This should work:
stuff.LoadCompleted += (sender, args) => tcs.TrySetResult(args.Result);
stuff.LoadCompleted += (sender, args) => tcs.TrySetResult(args.Result);
This should fix your problem
If you look at EventHandler<T> definition you will see it takes two arguments
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
So you need to pass two arguments in your assignment
stuff.LoadCompleted += (sender, args) => tcs.TrySetResult(args.Result);
I use .net 4.0 and i've tried to figure out how to use async method to await DocumentCompleted event to complete and return the value. My original code is above, how can i turn it into async/await model in this scenario ?
private class BrowserWindow
{
private bool webBrowserReady = false;
public string content = "";
public void Navigate(string url)
{
xxx browser = new xxx();
browser.DocumentCompleted += new EventHandler(wb_DocumentCompleted);
webBrowserReady = false;
browser.CreateControl();
if (browser.IsHandleCreated)
browser.Navigate(url);
while (!webBrowserReady)
{
//Application.DoEvents(); >> replace it with async/await
}
}
private void wb_DocumentCompleted(object sender, EventArgs e)
{
try
{
...
webBrowserReady = true;
content = browser.Document.Body.InnerHtml;
}
catch
{
}
}
public delegate string AsyncMethodCaller(string url);
}
So we need a method that returns a task when the DocumentCompleted event fires. Anytime you need that for a given event you can create a method like this:
public static Task WhenDocumentCompleted(this WebBrowser browser)
{
var tcs = new TaskCompletionSource<bool>();
browser.DocumentCompleted += (s, args) => tcs.SetResult(true);
return tcs.Task;
}
Once you have that you can use:
await browser.WhenDocumentCompleted();
#Servy had the genius answer I was looking for, but it didn't apply to my use case well. I found errors when the event is raised multiple times due to the event handler trying to set the result on the TaskCompletionSource on subsequent event invocations.
I enhanced his answer in two ways. The first is simply to unsubscribe the DocumentCompleted event once it has been handled the first time.
public static Task WhenDocumentCompleted(this WebBrowser browser)
{
var tcs = new TaskCompletionSource<bool>();
browser.DocumentCompleted += DocumentCompletedHandler;
return tcs.Task;
void DocumentCompletedHandler(object sender, EventArgs e)
{
browser.DocumentCompleted -= DocumentCompletedHandler;
tcs.SetResult(true);
}
}
Note I'm using a local function here to capture the TaskCompletionSource instance, which requires a minimum of C# 7.0.
The second enhancement is to add a timeout. My particular use case was in unit tests, and I wanted to make waiting for my particular event deterministic and not wait indefinitely if there was a problem.
I chose to use a timer for this, and set it to fire only once, then stop the timer when it is no longer needed. Alternatively could leverage a CancellationToken here to manage the TaskCompletionSource but I feel this requires maintainers to know more about their usage and timers are more universally understood.
public static Task WhenDocumentCompleted(this WebBrowser browser, int timeoutInMilliseconds = 500)
{
var tcs = new TaskCompletionSource<bool>();
var timeoutTimer = new System.Timers.Timer(timeoutInMilliseconds);
timeoutTimer.AutoReset = false;
timeoutTimer.Elapsed += (s,e) => tcs.TrySetCanceled();
timeoutTimer.Start();
browser.DocumentCompleted += DocumentCompletedHandler;
return tcs.Task;
void DocumentCompletedHandler(object sender, EventArgs e)
{
timeoutTimer.Stop();
browser.DocumentCompleted -= DocumentCompletedHandler;
tcs.TrySetResult(true);
}
}
Note to ensure the code is thread safe I've gone more defensive here and used Try... functions. This ensures there are no errors setting the result even when edge-case interleaved execution occurs.
After asking this question, I am wondering if it is possible to wait for an event to be fired, and then get the event data and return part of it. Sort of like this:
private event MyEventHandler event;
public string ReadLine(){ return event.waitForValue().Message; }
...
event("My String");
...elsewhere...
var resp = ReadLine();
Please make sure whatever solution you provide returns the value directly rather than getting it from something else. I'm asking if the method above is available in some way. I know about Auto/ManuelResetEvent, but I don't know that they return the value directly like I did above.
Update: I declared an event using MyEventHandler (which contains a Message field). I have a method in another thread called ReadLine waiting for the event to fire. When the event fires the WaitForValue method (part of the event handling scene) returns the event args, which contains the message. The message is then returned by ReadLine to whatever had called it.
The accepted answer to that question I asked was what I did, but it just doesn't feel quite right. It almost feels like something could happen to the data between the ManuelResetEvent firing and the program retrieving the data and returning it.
Update: The main problem with the Auto/ManualResetEvent is that it is too vulnerable. A thread could wait for the event, and then not give enough time for anyone else to get it before changing it to something else. Is there a way to use locks or something else? Maybe using get and set statements.
If the current method is async then you can use TaskCompletionSource. Create a field that the event handler and the current method can access.
TaskCompletionSource<bool> tcs = null;
private async void Button_Click(object sender, RoutedEventArgs e)
{
tcs = new TaskCompletionSource<bool>();
await tcs.Task;
WelcomeTitle.Text = "Finished work";
}
private void Button_Click2(object sender, RoutedEventArgs e)
{
tcs?.TrySetResult(true);
}
This example uses a form that has a textblock named WelcomeTitle and two buttons. When the first button is clicked it starts the click event but stops at the await line. When the second button is clicked the task is completed and the WelcomeTitle text is updated. If you want to timeout as well then change
await tcs.Task;
to
await Task.WhenAny(tcs.Task, Task.Delay(25000));
if (tcs.Task.IsCompleted)
WelcomeTitle.Text = "Task Completed";
else
WelcomeTitle.Text = "Task Timed Out";
You can use ManualResetEvent. Reset the event before you fire secondary thread and then use the WaitOne() method to block the current thread. You can then have secondary thread set the ManualResetEvent which would cause the main thread to continue. Something like this:
ManualResetEvent oSignalEvent = new ManualResetEvent(false);
void SecondThread(){
//DoStuff
oSignalEvent.Set();
}
void Main(){
//DoStuff
//Call second thread
System.Threading.Thread oSecondThread = new System.Threading.Thread(SecondThread);
oSecondThread.Start();
oSignalEvent.WaitOne(); //This thread will block here until the reset event is sent.
oSignalEvent.Reset();
//Do more stuff
}
A very easy kind of event you can wait for is the ManualResetEvent, and even better, the ManualResetEventSlim.
They have a WaitOne() method that does exactly that. You can wait forever, or set a timeout, or a "cancellation token" which is a way for you to decide to stop waiting for the event (if you want to cancel your work, or your app is asked to exit).
You fire them calling Set().
Here is the doc.
If you're happy to use the Microsoft Reactive Extensions, then this can work nicely:
public class Foo
{
public delegate void MyEventHandler(object source, MessageEventArgs args);
public event MyEventHandler _event;
public string ReadLine()
{
return Observable
.FromEventPattern<MyEventHandler, MessageEventArgs>(
h => this._event += h,
h => this._event -= h)
.Select(ep => ep.EventArgs.Message)
.First();
}
public void SendLine(string message)
{
_event(this, new MessageEventArgs() { Message = message });
}
}
public class MessageEventArgs : EventArgs
{
public string Message;
}
I can use it like this:
var foo = new Foo();
ThreadPoolScheduler.Instance
.Schedule(
TimeSpan.FromSeconds(5.0),
() => foo.SendLine("Bar!"));
var resp = foo.ReadLine();
Console.WriteLine(resp);
I needed to call the SendLine message on a different thread to avoid locking, but this code shows that it works as expected.
Try it : e.Handled = true; It works to prevent KeyEventArgs, for example.
Sorry to ask all, but I'm an old hand Vb.net guy who's transferring to c#. I have the following piece of code that seems to activate when the (in this case) postAsync method is fired. I just don;t understand what the code is doing (as follows):-
app.PostCompleted +=
(o, args) =>
{
if (args.Error == null)
{
MessageBox.Show("Picture posted to wall successfully.");
}
else
{
MessageBox.Show(args.Error.Message);
}
};
if anyone could explain what the += (o,args) => is actually acheiving I'd be so greatful....
many thanks in advance.
Tim
(o,args) => defines a lambda expression that takes two parameters named o and args. The types of those parameters is inferred according to the type of PostCompleted (if PostCompleted is an EventHandler, then they will be respectively of type Object and EventArgs). The expression's body then follows after the =>.
The result is than added as an handler to PostCompleted.
As such, it's a less verbose way to write:
app.PostCompleted += delegate(object o, EventArgs args)
{
// ...
};
Which is a shorthand for:
void YourHandler(object o, EventArgs args)
{
// ...
}
// ...
app.PostCompleted += YourHandler;
That is an added handler for the PostCompleted event using a lambda expression. It is similar to
app.PostCompleted += MyHandler;
// ...
private void MyHandler(object sender, EventArgs e) {
// ...
}
But when using lambda expressions, you can't detach the handler easily.
It's shorthand for a delegate defining the event handler for the POST completion event:
app.PostCompleted += delegate(object o, EventArgs args) {
// ...
};
See also Anonymous Methods.
Assuming PostCompleted is an event, you are basically creating an event handler using lambda notation.
This code snipped is equivalent to:
app.PostCompleted += delegate (o, args)
{
if (args.Error == null)
{
MessageBox.Show("Picture posted to wall successfully.");
}
else
{
MessageBox.Show(args.Error.Message);
}
};
The vb.net equivalent would look like this:
AddHandler app.PostCompleted, Function(o, args)
''# ...
End Function
Note that this requires Visual Studio 2010/.Net 4, but the C# code works back in Visual Studio 2008/.Net 3.5.
But that's only partly true. In C#, this is a way to define a method as an event handler in one place. In VB.Net, you can use the Handles keyword, and so the actual equivalent might look more like this:
Public Sub App_PostCompleted(ByVal Sender as Object, ByVall e As EventArgs) Handles app.PostCompleted
''#
End Sub
But even that's not completely equivalent, since you gave the method a name and can call it from anywhere. The only reference to the C# code (and thus the only way to call it) is through the event subscription.
The (o,args) => part is a lambda expression, which is an anonymous function.
the += part assigns the lambda expression to be called when the event fires.