Uno platform: invoke UI update from async thread - async-await

I have an async member which ultimately needs to invoke some UI updates, after getting some data from a server.
I think I need something like BeginInvokeOnMainThread, or Dispatcher.Invoke, but neither of these appear to be recognized in the Uno context.
Here's the essence of what I have:
public async Task LoadList()
{
...
// get data
Uri uri = new Uri("https://...");
response = await httpClient.GetAsync(uri);
// display
BeginInvokeOnMainThread () =>
{
... update the UI ...
});
}
But I get the error CS0103 The name 'BeginInvokeOnMainThread' does not exist in the current context UnoTest.Droid, UnoTest.UWP, UnoTest.Wasm

BeginInvokeOnMainThread / Dispatcher.Invoke / Control.Invoke were never a good idea.
Uno should have a SynchronizationContext that is automatically used by await, so manual thread marshaling should not be necessary:
public async Task LoadList()
{
...
// get data
Uri uri = new Uri("https://...");
response = await httpClient.GetAsync(uri);
// display
... update the UI ...
}

Related

PostAsync hanging in Xamarin Forms works on emulator but hangs on actual Mobile phone

I have Xamarin Forms project where I'm trying to POST and GET data to/from a Web API but when I'm making an async/await call, it works on the emulator (not without its original problems!) but when I try it on my actual phone mobile (Samsung S8+), it just hangs indefinitely.
Note that I'm only concentrating on the Android part right now, not iOS, not that the problem should make any difference in either.
This is the code I'm using:
IDataService.cs
Task<TResponse> PostDataAsync<TRequest, TResponse>(string uri, TRequest data)
where TRequest : class
where TResponse : class;
DataService.cs:
public async Task<TResponse> PostDataAsync<TRequest, TResponse>(string
additionalUri, TRequest data)
where TRequest : class
where TResponse : class
{
return await WebClient
.PostData<TRequest, TResponse>
(string.Concat(this.Uri, additionalUri), data);
}
WebClient.cs
using (var client = new HttpClient())
{
var jsonData = JsonConvert.SerializeObject(data);
using (var response = await client.PostAsync(
uri,
new StringContent(jsonData,
Encoding.UTF8,
"application/json" )))
{
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<TResponse>(content);
}
}
}
Method 1:
LoginPageViewModel.cs
public DelegateCommand SignInCommand => _signInCommand ??
(this._signInCommand = new DelegateCommand(SignInCommandAction));
private async void SignInCommandAction()
{
try
{
....
var user = await this._dataService
.PostDataAsync<LoginRequestDto,
LoginResponseDto>(#"Accounts/Login", loginRequestDto);
....
}
...
}
Method2:
LoginPageViewModel.cs
public DelegateCommand SignInCommand => _signInCommand ??
(this._signInCommand =
new DelegateCommand(async () => await SignInCommandAction()));
private async Task SignInCommandAction()
{
try
{
....
var user = await this._dataService
.PostDataAsync<LoginRequestDto,
LoginResponseDto>(#"Accounts/Login", loginRequestDto);
....
}
...
}
The PostDataAsync works with both methods when I call my local web API i.e. http://10.0.2.2/MyApp/api/ but both methods still hangs when calling external my web service from web provider i.e. http://myapp-123-site.atempurl.com/api/ which is a temp url for testing purpose.
The same apply to my GetDataAsync which is not demonstrated in question but I just thought I'd mention it.
Based on the above, you would think that my async/await code is correct since it works when calling the local web api but then what's causing it to hang when calling the remote web api.
As mentioned, I did enable my INTERNET permission in the manifest.
Any suggestions welcomed?
Thanks.
UPDATE-1:
Note that I've just tried to call a GET opertation within the same function and this is working in the emulator but hanging with the actual mobile.
using (var client = new HttpClient())
{
using (var response = await client.GetAsync(uri))
{
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
return Newtonsoft.Json.JsonConvert
.DeserializeObject<TResponse>(content);
}
}
}
UPDATE-2:
This is somehow working and I have no idea why! The only thing that comes to mind is that I upgraded my libraries. This included PRISM which may have been at the source of the problem but I have no idea.
Sorry I can't provide more details. I could role back my code and try to see if it's hanging again but I just don't have the time to go and experiment some more considering the amount of time I've already spent on this. Sorry.
The requested url is an IP or a domain name.
If it is ip, only the IP of the public network can be accessed by devices on multiple network segments.
If it is a domain name, it needs to support the domain name resolution service.
If you do not have these environments for a while, you need the IP of the device and the IP of the server on the same network segment.
The PostDataAsync works with both methods when I call my local web API i.e. http://10.0.2.2/MyApp/api/ but both methods still hangs when calling external my web service from web provider i.e. http://myapp-123-site.atempurl.com/api/ which is a temp url for testing purpose.
From this phenomenon , the reason should be the temp url. From this domain name (myapp-123-site.atempurl.com) can not find the right local IP (10.0.2.2).And when you test in local network , I guess this will work.However the network of actual mobile can be not the same with local network , such as using 3G/4G network , then this will not working.

PublishRequest<T> IBus extension equivalent in v5.x

With v5 PublishRequest extension was removed from the IBus interface.
We used the callback to handle multiple response types that could be returned from the consumer ( Faults, Validations, actual responses, etc )
What is the equivalent way of publishing a message and wiring up multiple response types ?
// Request/Response contracts, may also return validation failure or fault contract
Request<TMessage> request = await bus.PublishRequest<TMessage>( msg, context => {
context.Handle<TResponse>( value => ... );
context.Handle<TValidation>( value => ... );
context.Handle<Fault>( value => ... );
context.CorrelationId = ...
context.Headers.Set( ... );
});
await request.Task;
You can use the new syntax, which is much cleaner overall.
var client = Bus.CreateRequestClient<RegisterMember>();
var (registered, existing) =
await client.GetResponse<MemberRegistered, ExistingMemberFound>(
new RegisterMember() {MemberId = "Johnny5"});
This will return either of the two responses, and if a fault occurs, either will throw the faulted request exception.
You can also use a request handle to add headers, etc.
var client = Bus.CreateRequestClient<RegisterMember>();
var request = client.Create(new RegisterMember()
{MemberId = "Johnny5"});
// the request is also the send pipe configurator, so...
request.UseExecute(context => context.CorrelationId = someId);
var (registered, existing) =
await request.GetResponse<MemberRegistered, ExistingMemberFound>();
You can see a working test case in the future tests:
https://github.com/MassTransit/MassTransit/blob/develop/src/MassTransit.Futures.Tests/Request_Specs.cs#L170

How to make https webAPI call?

I'm trying to make https webAPI call, specifically - Google Directions API. Putting the uri directly inside browser gives me the result that I want, so I'm 100% sure my uri is correct.
Now, how do I call the webapi inside my PCL? Using modernhttp and HttpClient now, but am open to whatever options there are out there.
private async Task<string> GetJsonObjFromUrl(string urlRoutes)
{
HttpClient c = new HttpClient(new NativeMessageHandler());
var resp = await c.SendAsync(new HttpRequestMessage(HttpMethod.Get, new Uri(urlRoutes)));
if (resp.IsSuccessStatusCode)
{
var json = await resp.Content.ReadAsStringAsync();
return json;
}
return null;
}
What am I doing wrong?
Edit: Just putting this here because this was driving me crazy whole night. Ends up the caller way, way above forgot to put await. The execution continues straight after and never returns to get the result. That's why I never got any results... :\
The code just don't go hit anywhere below client.SendAsync / GetStringAsync
I suspect that further up your call stack, your code is calling Result / Wait / GetAwaiter().GetResult() on a task. If called from a UI thread, this will deadlock, as I explain on my blog.
The deadlock is caused by the async method attempting to resume on the UI context, but the UI thread is blocked waiting for the task to complete. Since the async method must complete in order to complete its task, there's a deadlock.
The proper fix is to replace that Result / Wait with await.
In your PCL use:
HttpClient httpClient = new HttpClient();
var json = await httpClient.GetStringAsync(Url);
In case of using HTTPS.
In Android, your main activity:
protected override void OnCreate(Bundle bundle)
{
ServicePointManager.ServerCertificateValidationCallback +=(sender, cert, chain, sslPolicyErrors) => true;
}
In iOS, in your AppDelegate:
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
return base.FinishedLaunching(app, options);
}

How do I use async and await

So here is the basic of what I am trying to do...
I have create a Web site that has web API 2 so I can do crud operations to that site.
I am trying to create the client side application in VS 2013. I have created a basic Console apps to start the project.
class PortalReposotry
{
private Uri _uri;
public PortalReposotry()
{
_uri = new Uri("http://localhost:21564/");
}
public async Task<CompanyAPI> GetCompany(string companyCode)
{
var client = new HttpClient();
client.BaseAddress = _uri;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await client.GetAsync(String.Format("api/companies/{0}", companyCode));
CompanyAPI content = await response.Content.ReadAsAsync<CompanyAPI>();
return await Task.Run(() => content);
}
}
class Program
{
static void Main(string[] args)
{
PortalReposotry repo = new PortalReposotry();
CompanyAPI comp = repo.GetCompany("LNCR");
Console.ReadKey();
}
}
I have no Idea why I have to use a lamda expression to return the CompanyAPI object.
All I want returned in the CompanyAPI object not the task it is running on. I am very confused on the threading and why I have to run this under task... it makes where I have to wrap everything into a task.... Which I'll be honest I have no idea how to use or decuple the actual objects from it that I want.
If you can help me out, I could be going the wrong direction all together but this is what I have found so far.
I am very confused on the threading and why I have to run this under task
You don't have to use threading here (the Task.Run is unnecessary). The Task<T> type is a "future" - it represents an asynchronous operation that will have a result value of type T in the future. That's why you need to use tasks with asynchronous code (technically, you could use callbacks instead, but that's much more painful - tasks are easier).
I have created a basic Console apps to start the project.
Asynchronous console apps are a bit weird. You need to block the main thread so the application doesn't exit. This is unnecessary in a real UI client-side app (which I assume your sample project will eventually become). So for now you can just do a hack like this:
static void Main(string[] args)
{
MainAsync().Wait();
}
static async Task MainAsync()
{
PortalReposotry repo = new PortalReposotry();
CompanyAPI comp = await repo.GetCompanyAsync("LNCR");
Console.ReadKey();
}
The call to Wait() should only be done inside a Console app's Main method. You shouldn't ever call it in a UI application.
Since Task.Run isn't needed, you can clean up that method, too:
public async Task<CompanyAPI> GetCompanyAsync(string companyCode)
{
var client = new HttpClient();
client.BaseAddress = _uri;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await client.GetAsync(String.Format("api/companies/{0}", companyCode));
return await response.Content.ReadAsAsync<CompanyAPI>();
}
in your GetCompany method, the await and Task.Run is unnecessary, you can just return the content.
public async Task<CompanyAPI> GetCompany(string companyCode)
{
var client = new HttpClient();
client.BaseAddress = _uri;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await client.GetAsync(String.Format("api/companies/{0}", companyCode));
CompanyAPI content = await response.Content.ReadAsAsync<CompanyAPI>();
return content
}
then in your main method you would just call it like so. Disclaimer,
this will make it run synchronously. You can use the ContinueWith method of the Task object to add a "callback" to the task. https://msdn.microsoft.com/en-us/library/system.threading.tasks.task.continuewith(v=vs.110).aspx
CompanyAPI comp = repo.GetCompany("LNCR").Result;

SmtpClient.SendAsync blocking my ASP.NET MVC Request

I have a Action that sends a simple email:
[HttpPost, ActionName("Index")]
public ActionResult IndexPost(ContactForm contactForm)
{
if (ModelState.IsValid)
{
new EmailService().SendAsync(contactForm.Email, contactForm.Name, contactForm.Subject, contactForm.Body, true);
return RedirectToAction(MVC.Contact.Success());
}
return View(contactForm);
}
And a email service:
public void SendAsync(string fromEmail, string fromName, string subject, string body, bool isBodyHtml)
{
MailMessage mailMessage....
....
SmtpClient client = new SmtpClient(settingRepository.SmtpAddress, settingRepository.SmtpPort);
client.EnableSsl = settingRepository.SmtpSsl;
client.Credentials = new NetworkCredential(settingRepository.SmtpUserName, settingRepository.SmtpPassword);
client.SendCompleted += client_SendCompleted;
client.SendAsync(mailMessage, Tuple.Create(client, mailMessage));
}
private void client_SendCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
Tuple<SmtpClient, MailMessage> data = (Tuple<SmtpClient, MailMessage>)e.UserState;
data.Item1.Dispose();
data.Item2.Dispose();
if (e.Error != null)
{
}
}
When I send a email, I am using Async method, then my method SendAsync return immediately, then RedirectToAction is called. But the response(in this case a redirect) isnĀ“t sent by ASP.NET until client_SendCompleted is completed.
Here's what I'm trying to understand:
When watching the execution in Visual Studio debugger, the SendAsync returns immediately (and RedirectToAction is called), but nothing happens in the browser until email is sent?
If i put a breakpoint inside client_SendCompleted, the client stay at loading.... until I hit F5 at debugger.
This is by design. ASP.NET will automatically wait for any outstanding async work to finish before finishing the request if the async work was kicked off in a way that calls into the underlying SynchronizationContext. This is to ensure that if your async operation tries to interact with the HttpContext, HttpResponse, etc. it will still be around.
If you want to do true fire & forget, you need to wrap your call in ThreadPool.QueueUserWorkItem. This will force it to run on a new thread pool thread without going through the SynchronizationContext, so the request will then happily return.
Note however, that if for any reason the app domain were to go down while your send was still in progress (e.g. if you changed the web.config file, dropped a new file into bin, the app pool recycled, etc.) your async send would be abruptly interrupted. If you care about that, take a look at Phil Haacks WebBackgrounder for ASP.NET, which let's you queue and run background work (like sending an email) in such a way that will ensure it gracefully finishes in the case the app domain shuts down.
This is an interesting one. I've reproduced the unexpected behaviour, but I can't explain it. I'll keep digging.
Anyway the solution seems to be to queue a background thread, which kind of defeats the purpose in using SendAsync. You end up with this:
MailMessage mailMessage = new MailMessage(...);
SmtpClient client = new SmtpClient(...);
client.SendCompleted += (s, e) =>
{
client.Dispose();
mailMessage.Dispose();
};
ThreadPool.QueueUserWorkItem(o =>
client.SendAsync(mailMessage, Tuple.Create(client, mailMessage)));
Which may as well become:
ThreadPool.QueueUserWorkItem(o => {
using (SmtpClient client = new SmtpClient(...))
{
using (MailMessage mailMessage = new MailMessage(...))
{
client.Send(mailMessage, Tuple.Create(client, mailMessage));
}
}
});
With .Net 4.5.2, you can do this with ActionMailer.Net:
var mailer = new MailController();
var msg = mailer.SomeMailAction(recipient);
var tcs = new TaskCompletionSource<MailMessage>();
mailer.OnMailSentCallback = tcs.SetResult;
HostingEnvironment.QueueBackgroundWorkItem(async ct =>
{
msg.DeliverAsync();
await tcs.Task;
Trace.TraceInformation("Mail sent to " + recipient);
});
Please read this first: http://www.hanselman.com/blog/HowToRunBackgroundTasksInASPNET.aspx
I sent the bug to Microsoft Connect https://connect.microsoft.com/VisualStudio/feedback/details/688210/smtpclient-sendasync-blocking-my-asp-net-mvc-request

Resources