How I can safely run async code, when handling messages from Messenger? - asynchronous

I am using MVVM, inparticular MVVMLight. For boradcasting to all of my modelviews, that no internet connection is available I am using Messenger class. The modelviews subscribe to this event in order to reload itself with offline data, inform user etc.
However, I have a problem. When I have the folowing handler:
private void HandleNoInternetMessage(NoInternetAccessMessage obj)
{
Task.Run(async () => await InitializeForOfflineInternalAsync());
}
public async Task InitializeForOfflineInternalAsync()
{
try
{
WaitingLayerViewModel.ShouldBeVisible = true;
WaitingLayerViewModel.IsBusy = true; //<--exception HRESULT: 0x8001010E (RPC_E_WRONG_THREAD)
bool switchToOffline = await CommonViewModelProvider.InformUserOfNoInternetAccessAndChangeAppState(); //<!- CoreWindow.GetForCurrentThread().Dispatcher is null
await FilterTestItemViewModel.InitializeForOfflineAsync();
await FilterTestItemViewModel.InitializeForOfflineAsync();
WaitingLayerViewModel.ShouldBeVisible = false;
WaitingLayerViewModel.IsBusy = false;
...
}
}
I got exception HRESULT: 0x8001010E (RPC_E_WRONG_THREAD), because in InitializeForOfflineInternalAsync I am changing some properties of the viewmodel wchich are bound in XAML (or at least I think it is because of that). However, it is weird, because I am changing in other code bound properties regularly and have no problems with it (and the thread is a working thread).
Now, how can i solve that?
The messanger let me provide only delegate which is not async (which make kind of sense), so I can not have the HandleNoInternetMessage method async
I am using async await ... no explicit spawning of threads
I dont have access in VM to Dispatcher, because I am in VM which should not know about platform dependent stuff. And when I tried to use it to show a message, NullPointer excpetion was thrown when calling CoreWindow.GetForCurrentThread().Dispatcher; And again when calling from other places, no such exception was thrown
I guess the question is How I can safely run async code, which changes boudn properties, when handling messages from Messenger?

You're responding to messages that are logically events, so this is an acceptable use case for async void.
private async void HandleNoInternetMessage(NoInternetAccessMessage obj)
{
await InitializeForOfflineInternalAsync();
}
public async Task InitializeForOfflineInternalAsync()
{
try
{
WaitingLayerViewModel.ShouldBeVisible = true;
WaitingLayerViewModel.IsBusy = true;
bool switchToOffline = await CommonViewModelProvider.InformUserOfNoInternetAccessAndChangeAppState();
await FilterTestItemViewModel.InitializeForOfflineAsync();
await FilterTestItemViewModel.InitializeForOfflineAsync();
WaitingLayerViewModel.ShouldBeVisible = false;
WaitingLayerViewModel.IsBusy = false;
...
}
}
Remember that Task.Run is for CPU-bound code (as I describe on my blog).

Related

Cannot use CancellationToken to end orchestrator function

I am using an orchestration to await two sub-orchestrations which are executing in parallel or a timeout of 5min, whatever comes first. I am passing a CancellationToken to the sub-orchestrators and therein throw on cancellation using token.ThrowIfCancellationRequested(). Also, since I have created a timer in the main orchestrator function, I am responsible to clear it in the case the timeout is not triggered itself. This is documented here.
main orchestrator:
public class Fxbm_workflow
{
[FunctionName(nameof(Fxbm_workflow))]
public async Task Run([OrchestrationTrigger] IDurableOrchestrationContext ctx, ILogger log)
{
log = ctx.CreateReplaySafeLogger(log);
var parentWorkflowId = ctx.InstanceId;
var trigger = ctx.GetInput<Trigger<OrchestrationInput2>>();
using var cts = new CancellationTokenSource();
var expireIn = ctx.CurrentUtcDateTime.AddMinutes(5);
var responseTasks = new [] {327, 41}.Select(id => {
var input = (id, cts.Token);
var childWorkflowId = $"{parentWorkflowId}_{nameof(Fxbm_receive_response_workflow)}_{id}";
return ctx.CallSubOrchestratorAsync<object>(nameof(Fxbm_receive_response_workflow), instanceId: childWorkflowId, input: input);
});
var timeoutTask = ctx.CreateTimer(expireIn, cts.Token);
// await tasks, choose winner
var winner = await Task.WhenAny(Task.WhenAll(responseTasks), timeoutTask);
if (winner == timeoutTask)
{
log.LogWarning("winner= timeout");
// no need to cts.Cancel() here
}
else
{
log.LogWarning("winner= all tasks have finished before the timeout");
cts.Cancel();
}
}
}
sub-orchestrator function:
public class Fxbm_receive_response_workflow
{
[FunctionName(nameof(Fxbm_receive_response_workflow))]
public async Task<object> Run([OrchestrationTrigger] IDurableOrchestrationContext ctx, ILogger log)
{
log = ctx.CreateReplaySafeLogger(log);
var (clientId, token) = ctx.GetInput<(int, CancellationToken)>();
token.ThrowIfCancellationRequested();
log.LogWarning($"waiting for response of clientId= {clientId}");
var evnt = await ctx.WaitForExternalEvent<object>(name: clientId.ToString());
log.LogWarning($"response received for clientId= {clientId}");
await ctx.CallActivityAsync(nameof(Fxbm_notifyOfNewReport), evnt);
return evnt;
}
}
However, this doesn't work. In the screenshot below there's the execution history extracted using DfMon. One of the two sub-orchestrations completes successfully. Before the other one can finish, the 5min timeout is triggered. Still, neither does the remaining sub-orchestration fail (due to token.ThrowIfCancellationRequested()), nor does the parent orchestration complete, inspite of calling cts.Cancel(). Both are still in a Running state.
It is my understanding the sub-orchestration should have failed/thrown and the parent orchestration should have concluded successfully. Does someone know why this is not the case here? Since orchestrator functions are re-executed again and again until finished, I do worry about unncessary $$$ cost of this when deployed in Azure.

Unable to Access SQLite Data in MvvmCross ViewModel

Hello StackOverflow community,
I know there's a lot of code in this post, but I wanted to give you guys, the community as good of a picture as possible as to what is going on here so that maybe someone can help me figure out what my issue is.
Recently for a project I'm working on we've decided to upgrade from MvvmCross 5.7.0 to 6.2.2. I've managed to get our UWP app to successfully complete the initialization and setup process. The first viewmodel for which we register the app start also starts initializing. However, I'm finding that my vm initialization hangs at a particular line of code (shown in the code below). The weirdest part though is similar methods called in the app initialization code run perfectly fine without hanging/deadlock, so I'm not sure what's different Here's a simplified version of my viewmodel code to illustrate:
public class MyViewModel : BaseAuthenticatedTabBarViewModel, IMvxViewModel<int>
{
private int? _settingValue;
public override async Task Initialize()
{
//Some irrelevant initialization code
Exception e = null;
try
{
//This line of code never returns
_settingValue = _settingValue ?? await AppSettingService.GetSettingValue();
}
catch (Exception ex)
{
e = ex;
}
if (e != null)
{
await HandleCatastrophicError(e);
}
}
}
The AppSettingService.GetSettingValue() method looks like this:
public async Task<int?> GetCurrentEventId()
{
return await GetNullableIntSetting("SettingValue");
}
private static async Task<int?> GetNullableIntSetting(string key)
{
try
{
var setting = await SettingDataService.SettingByName(key);
if (setting != null)
{
return string.IsNullOrEmpty(setting.Value) ? (int?)null : Convert.ToInt32(setting.Value);
}
}
catch (Exception ex)
{
//Handle the exception
}
return null;
}
All the code for SettingDataService:
public class SettingDataService : DataService<SettingDataModel>, ISettingDataService
{
public async Task<SettingDataModel> SettingByName(string name)
{
try
{
var values = (await WhereAsync(e => e.Name == name));
return values.FirstOrDefault();
}
catch(Exception ex)
{
//Handle the exception
}
return null;
}
}
Finally, the implementation for WhereAsync() is in a class called DataService and is as follows:
public virtual async Task<IEnumerable<T>> WhereAsync(System.Linq.Expressions.Expression<Func<T, bool>> condition, SQLiteAsyncConnection connection = null)
{
return await (connection ?? await GetConnectionAsync())
.Table<T>()
.Where(condition)
.ToListAsync();
}
Thank you very much for your help in advance
Edit: Forgot to also add this crucial bit of code to help you guys even further:
protected async Task<SQLiteAsyncConnection> GetConnectionAsync()
{
SQLiteAsyncConnection connection = null;
while (true)
{
try
{
connection = Factory.Create(App.DatabaseName);
// This line of code is the culprit. For some reason this hangs and I can't figure out why.
await connection.CreateTableAsync<T>();
break;
}
catch (SQLiteException ex)
{
if (ex.Result != Result.CannotOpen && ex.Result != Result.Busy && ex.Result != Result.Locked)
{
throw;
}
}
await Task.Delay(20);
}
return connection;
}
I'm suspecting that you are calling Task.Wait or Task<T>.Result somewhere further up your call stack. Or if you're not doing it, MvvmCross is probably doing it for you. This will cause a deadlock when called from a UI context.
Personally, I prefer the approach that ViewModels should always be constructed synchronously, and cannot have an asynchronous "initialization". That is, they must construct themselves (synchronously) into a "loading" state, and this construction can kick off an asynchronous operation that will later update them into a "loaded" state. The synchronous-initialization pattern means there's never an unnecessary delay when changing views; your users may only see a spinner or a loading message, but at least they'll see something. See my article on async MVVM data binding for a pattern that helps with this, and note that there's a newer version of the helper types in that article.

Web API Post not returning response

I have a Web API that gets call by this method:
public async Task<Model> AddModel(string token, Model newModel)
{
HttpContent content = await HttpHelper.Request(token, baseUrl, url, newModel, HttpRequestType.POST);
return await content.ReadAsAsync<Model>();
}
The Web API is successfully called and I can add a Model. This is the Web API method that gets called:
[Route("webapi/models/addModel")]
[HttpPost]
public async Task<ModelDto> AddWithoutResetingDefault(ModelDto newModel)
{
ModelService modelService = new ModelService();
return modelService.AddModel(newModel);
}
The problem is after the successful add, it doesn't return to the calling code anymore (I have a breakpoint that doesn't get hit). There are no console errors in the browser, I enclosed in a try-catch the calling code and the called code but there were no exceptions thrown.
Also, after the first click to add, if I try to refresh the browser, it takes a really long time to reload the browser (I don't know if being async has something to do with it).
(I don't know if being async has something to do with it) - Yes it has
your Api method
public async Task<ModelDto> AddWithoutResetingDefault(ModelDto newModel)
{
ModelService modelService = new ModelService();
return modelService.AddModel(newModel);
}
is marked as Async method, but the code inside is Sync. And that is the problem, if your modelService.AddModel(newModel); is async, then do
return await modelService.AddModel(newModel);
if its not then there is no point in making the AddWithoutResetingDefault
method async, hence remove Aysnc and simply do a sync method like
public ModelDto AddWithoutResetingDefault(ModelDto newModel)
{
ModelService modelService = new ModelService();
return modelService.AddModel(newModel);
}

SignalR An asynchronous operation error

I'm using SignalR 1.1.2 and I have problem with async hub method. Everything works fine on my PC with ForeverFrame transport but after deploying on server and switching to web sockets transport I receive following error:
An asynchronous operation cannot be started at this time. Asynchronous operations may only be started within an asynchronous handler or module or during certain events in the Page lifecycle. If this exception occurred while executing a Page, ensure that the Page is marked <%# Page Async="true" %>.
My hub method code:
public async Task<string> getUrl()
{
var url = await MyWebservice.GetMyRoomUrlAsync(Context.User.Identity.Name);
return url;
}
Are async methods supported in SignalR with web-sockets transport?
Update:
GetMyRoomUrlAsync code:
public static Task<string> GetMyRoomUrlAsync(string email)
{
var tcs = new TaskCompletionSource<string>();
var client = new Onif40.VisualStudioGeneratedSoapClient();
client.GetRoomUrlCompleted += (s, e) =>
{
if (e.Error != null)
tcs.TrySetException(e.Error);
else if (e.Cancelled)
tcs.TrySetCanceled();
else
tcs.TrySetResult(e.Result);
};
client.GetRoomUrlAsync(email);
return tcs.Task;
}
After Stephen Cleary clarified me where the problem was, solving it by rewriting EAP to APM was trivial.
public static Task<string> GetMyRoomUrlAsync(string email)
{
var tcs = new TaskCompletionSource<string>();
var client = new Onif40.VisualStudioGeneratedSoapClient();
client.BeginGetRoomUrl(email, iar =>
{
try
{
tcs.TrySetResult(client.EndGetRoomUrl(iar));
}
catch (Exception e)
{
tcs.TrySetException(e);
}
}, null);
return tcs.Task;
}
async methods are supported. However, you cannot use async void or async wrappers around EAP methods.
One common cause of this is using WebClient instead of the newer HttpClient. If that's not the case here, you would need to post the implementation of GetMyRoomUrlAsync.

Parallel exceptions are being caught

somehow my exceptions seem to being caught by method they are executing in. Here is the code to call the method. As you can see I create a cancellation token with a time out. I register a method to call when the cancellation token fires and then I start a new task. The cancellation token appears to be working OK. As does the registered method.
var cancellationToken = new CancellationTokenSource(subscriber.TimeToExpire).Token;
cancellationToken.Register(() =>
{
subscriber.Abort();
});
var task = Task<bool>.Factory.StartNew(() =>
{
subscriber.RunAsync((T)messagePacket.Body, cancellationToken);
return true;
})
.ContinueWith(anticedant =>
{
if (anticedant.IsCanceled)
{
Counter.Increment(12);
Trace.WriteLine("Request was canceled");
}
if (anticedant.IsFaulted)
{
Counter.Increment(13);
Trace.WriteLine("Request was canceled");
}
if (anticedant.IsCompleted)
{
Counter.Increment(14);
}
The next piece of code is the method that seems to be not only throwing the excetion (expected behavior. but also catching the exception.
public async override Task<bool> ProcessAsync(Message input, CancellationToken cancellationToken)
{
Random r = new Random();
Thread.Sleep(r.Next(90, 110));
cancellationToken.ThrowIfCancellationRequested();
return await DoSomethingAsync(input);
}
The exception is being thrown by the cancellation token but according to intellitrace it is being caught at the end of the method. I have tried a number of different options including throwing my own exception, but no matter what the continuewith function always executes the IsComleted or ran to completion code.
Any ideas on what I am doing wrong?
Thanks
I assume that RunAsync is the same as ProcessAsync.
The exception is being thrown by the cancellation token but according to intellitrace it is being caught at the end of the method.
Yup. Any async method will catch its own exceptions and place them on its returned Task. This is by design.
no matter what the continuewith function always executes the IsComleted or ran to completion code.
Well, let's take another look at the code:
var task = Task<bool>.Factory.StartNew(() =>
{
subscriber.RunAsync((T)messagePacket.Body, cancellationToken);
return true;
})
.ContinueWith(
Consider the lambda passed to StartNew: it calls RunAsync, it ignores the Task that it returns, and then it returns true (successfully). So the Task returned by StartNew will always return successfully. This is why the ContinueWith always executes for a successfully-completed task.
What you really want is to await the Task returned by RunAsync. So, something like this:
var task = Task.Run(async () =>
{
await subscriber.RunAsync((T)messagePacket.Body, cancellationToken);
return true;
})
.ContinueWith(
You're still ignoring the return value of RunAsync (the bool it returns is ignored), but you're not ignoring the Task itself (including cancellation/exception information).
P.S. I'm assuming there's a lot of code you're not showing us; using StartNew/Run to just kick off some async work and return a value is very expensive.
P.P.S. Use await Task.Delay instead of Thread.Sleep.

Resources