I am trying to have a callback function in xamarin forms to alert the user that the task is finished. I haven't figured it out yet and am not sure what is wrong.
Any help is appreciated with the proper way of doing this.
The code below is in a button click event. The method FinishPurchase never gets called.
Task<bool> task = StockDataController.Instance.WasItemPurchased(CloudInfo.IAPMonthlySubscriptionProductID);
Task continuation = task.ContinueWith(t=>FinishPurchase());
continuation.Wait();
Previously I was calling the code below in the button click event but I want to execute specific code after the method WasItemPurchased is executed,etc.
bool result=await StockDataController.Instance.WasItemPurchased(CloudInfo.IAPMonthlySubscriptionProductID)
The if statement, below, will be called once WasItemPurchased completes.
bool isResultSuccessful = await StockDataController.Instance.WasItemPurchased(CloudInfo.IAPMonthlySubscriptionProductID);
if (isResultSuccessful)
{
//Do Something
}
else
{
//Do Something else
}
Related
Trying to shorten things down, one button has this code (takes the first vacant slot and updates the DB as not vacant):
#onclick="#(e => UpdateTimeSlot(#FirstVacantSlot, "Booked", BookerGuid))"
and the other button has this code (user picks a vacant spot):
#onclick="#(e => UpdateTimeSlot(#selectedItem, "RowBooked", BookerGuid))"
Calling after update:
protected override async Task OnParametersSetAsync()
{
await RefreshDb(); // get list of todays timeslots from DB
StateHasChanged(); // well, Blazor magic refresh or whatever
}
FirstVacantSlot is set from a method that picks it from the DB.
selectedItem is set when clicking a row in a (MudBlazor) table.
Both get filled with the same type of data and send to the method UpdateTimeSlot.
Both buttons correctly updates the DB, but the table refreshes ONLY after use of the first button, not after the second button. Why?
Also, if I click the second button and then the first button the update no longer works there either.
Things I tried:
Debugged to see that the objects have identical values populated. Trying to follow whats happening.
StateHasChanged() in every corner of the code.
await this.OnParametersSetAsync() (Which makes the first button work)
Add; The UpdateTimeSlot method:
I substituted a call to RefreshDb() with the OnParameterAsync(). Still can't understand why it beahves differently depending on which button I click.
private async Task UpdateTimeSlot(TimeSlotDto vacantSlot, string title, Guid bookerId)
{
var updateTimeSlot = new TimeSlotDto();
updateTimeSlot.ID = (Guid)vacantSlot.ID;
updateTimeSlot.TimeSlotStart = vacantSlot.TimeSlotStart;
updateTimeSlot.TimeSlotEnd = vacantSlot.TimeSlotEnd;
updateTimeSlot.IsVacant = false;
updateTimeSlot.CreatedUTC = vacantSlot.CreatedUTC;
updateTimeSlot.Title = title;
updateTimeSlot.UpdatedUTC = DateTime.UtcNow;
updateTimeSlot.BookerId = bookerId;
await _http.PutAsJsonAsync($"https://localhost:44315/api/v1/TimeSlot/Update?id={updateTimeSlot.ID}", updateTimeSlot);
//await this.OnInitializedAsync();
await this.OnParametersSetAsync();
}
UPDATE
The problem has been located to the MudBlazor component Table, which isn't updating correctly after selecting a row in the table itself. Everything else is working as it should; I tried a foreach-loop to iterate through the IENumerable currentTimeSlots, that the MudTable also is dependent on and the foreach-loop updated correctly. So I guess it's a completely different question now.
Update - RefreshDb()
private async Task RefreshDb()
{
var roomClient = new RoomClient();
room = await roomClient.GetRoomByIdAndDateAsync(ThisRoomId, dtToday);
allTimeSlotsOfToday = room.TimeSlots;
currentTimeSlots = allTimeSlotsOfToday.GetAllCurrentTimeSlots(dtToday);
HasItems = currentTimeSlots.Count();
FirstVacantSlot = allTimeSlotsOfToday.GetFirstVacant(dtToday);
AnyVacantSlot = allTimeSlotsOfToday.Count(f => f.IsVacant);
}
Update:
For some this might help:
MudBlazor MudTable issue on GitHub
A bit of a trick to get the thread released is to add a Task.Delay. You shouldn't need to call OnParametersSetAsync (seems a bit kludgy).
Change your onclicks to something like:
#onclick="#(async () => await UpdateTimeSlotAsync(#FirstVacantSlot, "Booked", BookerGuid))"
Then an async handler:
private async Task UpdateTimeSlotAsync(...)
{
// update slot code...
await RefreshDb(); // get list of todays timeslots from DB
StateHasChanged(); // well, Blazor magic refresh or whatever
await Task.Delay(1); // let UI refresh
}
Firstly, never call OnInitialized{Async} or OnParametersSet{Async} manually. They are part of the ComponentBase infrastructure.
Try this:
You're calling await RefreshDb(); which I assume contains the code to do the refresh.
Update OnParametersSetAsync to:
protected override async Task OnParametersSetAsync()
{
await RefreshDb(); // get list of todays timeslots from DB
// StateHasChanged(); not required if RefreshDb is coded correctly.
}
UpdateTimeSlot should look like this:
private async Task UpdateTimeSlot(TimeSlotDto vacantSlot, string title, Guid bookerId)
{
....
await _http.PutAsJsonAsync($"https://localhost:44315/api/v1/TimeSlot/Update?id={updateTimeSlot.ID}", updateTimeSlot);
await RefreshDb();
// StateHasChanged(); if this fixes the problem then RefreshDb is not coded correctly.
}
You need to ensure RefreshDB is a proper async method that returns a Task that can be awaited, and any sub method calls are also awaited correctly. If a call to StateHasChanged at the end of UpdateTimeSlot fixes the problem then RefreshDb has an await problem.
Update your question with the RefreshDb code if necessary.
Mudtable is not updating it's content when you are in EditingMode. I could solve my StateHasChanged Issues with calling
mudTable.SetEditingItem(null);
This question already has answers here:
Is it possible to await an event instead of another async method?
(11 answers)
Closed 1 year ago.
I'm currently building an application where a user has a list of items that need to be triggered.
The operator triggers this using a shortcut ("ADD"-button).
Now most items only consist out of one action which is being executed, however, there are some items that include multiple steps. These steps need to be controlled by the operator.
So eventhough it is one item which is being triggered, the steps inside of the item need to be controlled by the shortcut.
private void function triggerItem()
{
// Perform Step 1
// PAUSE (AWAIT KEYPRESS)
// Perform Step 2
// PAUSE (AWAIT KEYPRESS)
// Perform Step 3
}
So for now in order to test it, I have just used a messagebox to "pause" in between the different steps, but I would like to make it a bit more clear. So I have created a panel with a keydown event that can capture the "ADD"-button being pressed to make the function continue.
But I think I would need a sort of Task that I can await the result for before continuing with the rest of the actions. Except I can't see how I can await a keydown event from a panel to return a value to the await function.
How could I achieve this?
private void showWarning()
{
// SHOW THE WARNING
panWarning.Show();
// ENSURE THE FOCUS IS SET TO THE PANEL TO CAPTURE THE KEY DOWN EVENT
panWarning.Focus();
// AWAIT THE KEYPRESS TO CONTINUE THE FUNCTION
}
private void panWarning_KeyDown(object sender, KeyEventArgs e)
{
// CHECK WHICH BUTTON WAS PRESSED
switch (e.KeyCode)
{
case Keys.Add:
// CONTINUE
break;
case Keys.Escape:
// ABORT
break;
}
}
Any suggestions?
Thank you.
You can use a TaskCompletionSource to await anything. To solve your problem, create one and set its result in an event handler.
A method like this one will do it:
public async Task WaitForKey(Control control)
{
var tcs = new TaskCompletionSource<bool>();
void LocalHandler(object s, EventArgs a)
{
tcs.SetResult(true);
}
control.KeyDown += LocalHandler;
await tcs.Task;
control.KeyDown -= LocalHandler;
}
And you'd call it like this:
private async Task TriggerItem()
{
// Perform Step 1
await WaitForKey(this);
// Perform Step 2
await WaitForKey(this);
// Perform Step 3
}
Note that if you are awaiting a keypress on the form itself, you may need to set KeyPreview to true.
I have a WinForms application that has two forms. On the first form the user enters some information and clicks the next button, this hides form1, loads form2 and fires a task (the function of which is on form1) that loads data from the database.
What I would like to do is on the second form have a text box that displays "Loading Data" whilst the task is running and then the count of the rows returned when the task has complete. All the while allowing the user to continue to enter data on form2.
This is the first time I have tried using tasks and async and I'm struggling to figure out a way to do this.
Task emailTask = new Task(() => FindCustomersForEmail(reg.Index));
emailTask.Start();
MessageControl formMessageControl = new MessageControl();
formMessageControl.Show();
this.Hide();
Whats in my head is something like:
while (emailTask not complete)
{
txtEmailCount.Text = "Loading";
}
txtEmailCount.Text = customersToEmail.Rows.Count.ToString();
Why not just await on the Task?
// Note this is void because I'm assuming it's an event handler.
// If it isn't this should be `async Task` instead.
public async void FindCustomersAsync()
{
txtEmailCount.Text = "Loading";
await Task.Run(() => FindCustomersForEmail(reg.Index));
txtEmailCount.Text = customersToEmail.Rows.Count.ToString();
}
Side note - prefer using Task.Run instead of manually starting the Task.
I added a the following extension method for photo chooser task and camera chooser task.
public static Task<TTaskEventArgs> ShowAsync<TTaskEventArgs>(this ChooserBase<TTaskEventArgs> chooser) where TTaskEventArgs : TaskEventArgs
{
var taskCompletionSource = new TaskCompletionSource<TTaskEventArgs>();
EventHandler<TTaskEventArgs> completed = null;
completed = (s, e) =>
{
chooser.Completed -= completed;
taskCompletionSource.SetResult(e);
};
chooser.Completed += completed;
chooser.Show();
return taskCompletionSource.Task;
}
And I invoked this method in my button click like this,
var photoResult = await new PhotoChooserTask().ShowAsync();
if (photoResult.TaskResult == TaskResult.OK)
{
// set the photo to image source.
}
After adding this every thing is working fine, But my issue is that while the time of invoking task by setting tombstone mode on, The code after my await is not executing (ie the completed event). How i can tackle this situation, I am expecting an answer that solve my issue on the above mentioned implementation(async / await). Not expecting the answer of registering event in the constructor.
I see you're following my article for this, it just turns out I forgot that my current solution doesn't work for tombstoning, as pointed out by someone in the article comments.
I'm preparing a fix for this, and I'll update the thread once I got it ready!
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.