ReactiveUI 6 Async Command Not Running on Background Thread in WPF app - c#

ViewModel
public class MyViewModel:ReactiveObject, IRoutableViewModel{
private ReactiveList<string> _appExtensions;
public MyViewModel(IScreen screen){
HostScreen = screen;
AppExtensions = new ReactiveList<string>();
GetApplicationExtensions =
ReactiveCommand.CreateAsyncTask(x => _schemaService.GetApplicationExtensions()); // returns a Task<IEnumerable<string>>
GetApplicationExtensions
.ObserveOn(RxApp.MainThreadScheduler)
.SubscribeOn(RxApp.TaskpoolScheduler)
.Subscribe(p =>
{
using (_appExtensions.SuppressChangeNotifications())
{
_appExtensions.Clear();
_appExtensions.AddRange(p);
}
});
GetApplicationExtensions.ThrownExceptions.Subscribe(
ex => Console.WriteLine("Error during fetching of application extensions! Err: {0}", ex.Message));
}
// bound to a ListBox
public ReactiveList<string> AppExtensions
{
get { return _appExtensions; }
set { this.RaiseAndSetIfChanged(ref _appExtensions, value); }
}
public ReactiveCommand<IEnumerable<string>> GetApplicationExtensions { get; protected set; }
}
and the View has a <Button Command="{Binding GetApplicationExtensions}"></Button>.
implentation of GetApplicationExtensions
public async Task<IEnumerable<string>> GetApplicationExtensions()
{
IEnumerable<string> extensions = null;
using (var client = new HttpClient())
{
client.BaseAddress = BaseAddress;
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken);
var response = await client.GetAsync("applications");
if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync();
extensions = JsonConvert.DeserializeObject<IEnumerable<string>>(json);
}
}
return extensions;
}
From everything I've read about ReactiveUI and all the examples I've seen (althought there are extremely few for the new 6.0+ versions), this should make my async call (which makes an async HTTP request via HttpClient) run on a background thread and update a ListBox in my view when the results are returned from it. However, this is not the case - the UI gets locked up for the duration of the async call. What am I doing wrong?
UPDATE
If I wrapped my HTTP call in a Task then everything worked as intended - the UI did not hang up at all. So the new implementation of my service call is this:
public Task<IEnumerable<string>> GetApplicationExtensions()
{
var extensionsTask = Task.Factory.StartNew(async () =>
{
IEnumerable<string> extensions = null;
using (var client = new HttpClient())
{
client.BaseAddress = BaseAddress;
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken);
var response = await client.GetAsync("applications");
if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync();
extensions = JsonConvert.DeserializeObject<IEnumerable<string>>(json);
}
}
return extensions;
}
return extensionsTask.Result;
}
Also, with this change to my async service call, I could remove the ObserveOn and SubscribeOn from my ReactiveCommand like #PaulBetts suggessted. So my ReactiveCommand implementation in my view model's constructor became this:
GetApplicationExtensions =
ReactiveCommand.CreateAsyncTask(x => _schemaService.GetApplicationExtensions()); // returns a Task<IEnumerable<string>>
GetApplicationExtensions
.Subscribe(p =>
{
using (_appExtensions.SuppressChangeNotifications())
{
_appExtensions.Clear();
_appExtensions.AddRange(p);
}
});

Can you show the implementation of _schemaService.GetApplicationExtensions()?
Depending on how it is implemented it might not actually be on another thread. Arguably, ReactiveCommand should guarantee that even async operations that accidentally burn CPU before running an async op are forced onto background threads, but efficiency is trumping defensive programming in this case.
You shouldn't need either SubscribeOn or ObserveOn, ReactiveCommand already guarantees that values will be returned on the UI thread. Otherwise, this code is looking good!

Change
GetApplicationExtensions
.ObserveOn(RxApp.MainThreadScheduler)
.SubscribeOn(RxApp.TaskpoolScheduler)
to
GetApplicationExtensions
.SubscribeOn(RxApp.TaskpoolScheduler)
.ObserveOn(RxApp.MainThreadScheduler)
ObserveOn should be after SubscribeOn

Related

HttpClient in async/sync implementation returns WaitingForActivation

I'm having a problem with async to sync implementation of HttpClient.
Id = 8, Status = WaitingForActivation, Method = "{null}", Result = "{Not yet computed}"
I know what I'm doing is probably a bad practice and it would be ideal to make all the path async, but that's a request that the company is making me, so I have to do like this.
Project is build in NET Standard 1.1, to be used as a NuGet package and to be compatible with Framework and Core as well.
Here's my main client construction...
private static HttpClient _client;
private static Uri _baseAddress;
private static readonly JsonSerializerSettings _settings = new JsonSerializerSettings
{ DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, MissingMemberHandling = MissingMemberHandling.Ignore };
public Client() { }
private Client(string baseUrl, Config config)
{
_baseAddress = new Uri(baseUrl);
_client = new HttpClient { Timeout = TimeSpan.FromSeconds(config.Timeout) };
_client.DefaultRequestHeaders.Accept.Clear();
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
_client.DefaultRequestHeaders.Add("X-API-KEY", config.Token);
}
private Client _paymentClient;
private Client _mainClient;
public Client Create(bool payment, Config config = null)
{
if (!payment)
{
_mainClient = _mainClient ?? new Client("https://api.address.com/", config);
return _mainClient;
}
_paymentClient = _paymentClient ?? new Client("https://payment.address.com/", config);
return _paymentClient;
}
public void Dispose() => _client.Dispose();
private static async Task<T> Send<T>(HttpMethod method, string url, object data = null)
{
var uri = new UriBuilder(_baseAddress);
uri.Path += url;
var request = new HttpRequestMessage(method, uri.Uri);
if (data != null)
request.Content = new StringContent(JsonConvert.SerializeObject(data, _settings), Encoding.UTF8, "application/json");
var response = await _client.SendAsync(request).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
T result = default;
if (response.IsSuccessStatusCode)
{
if (response.Content.Headers.ContentType.MediaType == "application/json")
{
var responseObj = JsonConvert.DeserializeObject<Response<T>>(content, _settings);
if (responseObj.HasError)
throw new Safe2PayException(responseObj.ErrorCode, responseObj.Error);
responseObj.ResponseDetail = result;
}
}
else throw new Exception((int) response.StatusCode + "-" + response.StatusCode);
request.Dispose();
response.Dispose();
return result;
}
And the Send<T> method is supposed to be a general treatment to process the request and response, wrapped on generic calls like this:
internal Task<T> Get<T>(string url) => Send<T>(HttpMethod.Get, url);
//OR even async...
internal async Task<T> Get<T>(string url) => await Send<T>(HttpMethod.Get, url);
Which are called like this, to send and receive data..
private Client Client { get; }
public CheckoutRequest(Config config) => Client = new Client().Create(true, config);
public object Credit(Transaction transaction)
{
var response = Client.Post<Transaction>("v2/Payment", transaction);
return response;
}
My problem is that the client is always getting me a WaitingfForActivation or even Running or WaitingToRun, doesn't matter if I change it to...
Task.Run(() => Send<T>(HttpMethod.Get, url));
//or
Task.Run(() => Send<T>(HttpMethod.Get, url).Result);
//or
Task.Run(async () => await Send<T>(HttpMethod.Get, url));
//or
Task.Run(async () => await Send<T>(HttpMethod.Get, url).ConfigureAwait(false));
I've been trying to find what I'm doing wrong, tried to change all the awaits, but I'm not being sucessful with this, so any help will be very much appreciated.
I suspect your problem is here:
public object Credit(Transaction transaction)
{
var response = Client.Post<Transaction>("v2/Payment", transaction);
return response;
}
You didn't show your code for Post<T>(), but I assume it's also an async Task<T> method, which means response is a Task<T> and your code is basically doing this:
Start a task.
Return a description of the incomplete task.
When I assume this is really what you want:
Start the task.
Wait for the task to complete.
Return the result of the task.
Ideally, this should be an async method, and you can await the task:
public async Task<object> Credit(Transaction transaction)
{
var response = await Client.Post<Transaction>("v2/Payment", transaction);
return response;
}
If you absolutely must wait for the task synchronously (there are very few reasons to need to) then you can use .GetAwaiter().GetResult():
public object Credit(Transaction transaction)
{
var response = Client.Post<Transaction>("v2/Payment", transaction).GetAwaiter().GetResult();
return response;
}
The main benefit of .GetAwaiter().GetResult() instead of .Result is that, in the case of exceptions, it will throw the actual exception instead of an AggregateException.
Also, you can make your Create() method static:
public static Client Create(bool payment, Config config = null)
Then you don't need to initialize the class just to call it:
public CheckoutRequest(Config config) => Client = Client.Create(true, config);
Update: If you want async and non-async versions of the same method, you can follow the same standard that Microsoft uses and name the async method with the Async suffix. The non-async version can just call the async version. For example:
public async Task<object> CreditAsync(Transaction transaction)
{
var response = await Client.Post<Transaction>("v2/Payment", transaction);
return response;
}
public object Credit(Transaction transaction)
{
return CreditAsync(transaction).GetAwaiter().GetResult();
}

InteropServices.COMException when adding item to ObservableCollection

I using signal R core to send me a list of messages but this happen
public ObservableCollection<ChatMessage> Messages { get; set; } = new ObservableCollection<ChatMessage>();
public async void InitSignalRAsync()
{
ChatMessage mess = new ChatMessage();
hubConnection = new HubConnectionBuilder().WithUrl("http://localhost:5000/chatHub").Build();
await hubConnection.StartAsync();
hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
{
mess.user = user;
mess.message = message;
Messages.Add(mess);
});
}
I got an error
System.Runtime.InteropServices.COMException:
at my
Messages.Add(mess);
when I receive the data
As Martin noted, you must update ViewModel components from their UI thread.
However, for a solution, I recommend using the more general-purpose SynchronizationContext rather than the UWP-specific Dispatcher class. By using the more general type, your code is more reusable and more testable.
E.g.:
public ObservableCollection<ChatMessage> Messages { get; set; } = new ObservableCollection<ChatMessage>();
public async Task InitSignalRAsync()
{
var context = SynchronizationContext.Current;
hubConnection = new HubConnectionBuilder().WithUrl("http://localhost:5000/chatHub").Build();
await hubConnection.StartAsync();
hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
{
var mess = new ChatMessage
{
user = user,
message = message,
};
context.Post(_ => Messages.Add(mess));
});
}
I also changed your async void to async Task (again, better reusability and testability), and made a new ChatMessage for each chat message, which I believe is the intended behavior.
This is happening because the Add() must run on the UI thread when you are using ObservableCollection. So to make it work, make sure to execute the call in Dispatcher.RunAsync():
Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {
Messages.Add(mess);
});

Struggling to get async working on deployment in ASP.net

The code works fine on my development environment, but in deployment with scallable architecture it appears to deadlock.
Objective here is to take a queue of API requests to send to SendGrid, batch them up and process each batch one at a time.
First call from ASHX handler
public void ProcessRequest(HttpContext context)
{
var result = Code.Helpers.Email.Sendgrid.Queue.Process().Result;
if (result.Success)
{
Queue.Process()
public static async Task<GenericMethodResult> Process()
{
var queueItems = GetQueueItemsToProcess();
var batches = BatchQueueItems(queueItems);
foreach (var batch in batches)
{
var r = await batch.SendToSendGrid();
if (r.StopBatch)
{
break;
}
}
return new GenericMethodResult(true);
}
SendToSendGrid()
public async Task<SendGridAPIMethodResponse> SendToSendGrid()
{
var r = new SendGridAPIMethodResponse();
var json = API.Functions.CreateJSONData(this);
var sg = new SendGridClient(Settings.Email.SendgridAPIKey);
dynamic response;
if (Action == Action.UpdateRecipient)
{
response = await sg.RequestAsync(SendGridClient.Method.PATCH, urlPath: "contactdb/recipients", requestBody: json);
}
string jsonResponse = response.Body.ReadAsStringAsync().Result;
// Process response...
return r;
}
I've stripped out as much of the code as I could.
Is anyone able to tell me why this code is timing out in production?
This blocking call to .Result in SendToSendGrid() is causing a deadlock as you are mixing async and blocking calls.
string jsonResponse = response.Body.ReadAsStringAsync().Result;
Use async all the way through
var jsonResponse = await response.Body.ReadAsStringAsync();
and try to avoid mixing blocking calls in async methods.
You should also conside making your handler async as well by using HttpTaskAsyncHandler.
public class MyHandler : HttpTaskAsyncHandler {
public override async Task ProcessRequestAsync(HttpContext context) {
var result = await Code.Helpers.Email.Sendgrid.Queue.Process();
if (result.Success) {
//..other code
}
}
}

Parallel Invoke with AwaitAsync in WebApi blocking

I've a webApi operation which executes 2 operations in || which internally invokes HttpClient sendAsync. If I apply debuggers and execute call, it works and returns. If I remove debuggers, both the async calls still work (checked in Fiddler) but caller of WebApi operation doesn't gets any response (using AdvanceRest chrome plugin). From the other threads, possibly I'm not using async/await correctly and related to ASP.NET synchronizationContext
//**WEB API Controller***
class SomeController
{
public HttpResponseMessage Get()
{
Client someClient = new Client();
aResponse = new aResponse();
bResponse = new bResponse();
Parallel.Invoke(
() => {aResponse = someClient.a()},
() => {bResponse = someClient.b()});
var response = {a=aResponse, b=bResponse};
return Response.Create(OK, response}
}
class SomeClient
{
AResponse a()
{
var clientResponse = ClientMgr.Execute("url");
return new AResponse {HttpClientResponse = clientResponse.Result}
}
BResponse b()
{
var clientResponse = ClientMgr.Execute("url");
return new BResponse {HttpClientResponse = clientResponse.Result}
}
}
//Utility CLASS
public class ClientMgr
{
public static async Task<HttpResponseMessage> Execute(string url)
{
request = new HttpRequestMessage();
//....request fill
HttpClient client = new HttpClient();
var response = await client.SendAsync(request);
client.dispose();
return response;
}
}
public class AResponse
{
HttpResponseMessage HttpClientResponse {get;set;}
// Some other properties....
}
Why does operation returns response when I'm using breakpoints but as I soon as I remove them, it doesn't returns response?
Your problem (other than the fact that the code you posted doesn't compile) is that while you debug, the async operations actually complete. When you don't debug, they don't, and it returns a Task<YourResponse>, not the actual result of the Task.
In order for this to work, mark your method as async and use Task.WhenAll to asynchronously wait on both tasks:
[HttpGet]
public async Task<HttpResponseMessage> GetAsync()
{
Client someClient = new Client();
var aTask = someClient.AAsync();
var bTask = someClient.BAsync();
await Task.WhenAll(aTask, bTask);
var response = { a = aTask.Result, b = bTask.Result };
return Response.Create(OK, response}
}
Side note - You don't need to use Paralle.Invoke when you have IO bound operations. Those are redundant threads which will be blocked waiting for the IO's completion.

ObjectDisposedException when trying to access Thread.CurrentPrincipal with async method

I'm fairly new to the new async/await stuff. However, I have the following classes:
public abstract class PluginBase
{
//Using EF to store log info to database
private EFContext _context = new EFContext();
private int Id = 1;
protected void LogEvent(string event, string details)
{
_context.LogEvents.Add(new LogItem(){
PluginId = this.Id,
Event = event,
Details = details,
User = Thread.CurrentPrincipal.Identity.Name
});
}
}
public class Plugin : PluginBase
{
public void Process()
{
CallWebService();
}
public async void CallWebService()
{
using(var http = new HttpClient())
{
...
var result = await http.PostAsync(memberURI, new StringContent(content, Encoding.UTF8,"application/json"));
if(result.IsSuccessStatusCode)
_status = "Success";
else
_status = "Fail";
LogEvent("Service Call - " + _status,...);
}
}
So, the idea is that Plugin.Process gets called. It in turn calls CallWebService(). CallWebService makes an asynchronous call to http.PostAsync. When I return from that call and try to call base.LogEvent(), I get an ObjectDisposedException stating that "Safe Handle has been Closed".
I know there is something going on where when the awaitable finishes, the rest of the code of the method has to be run. Maybe its being run in some other thread or context? If this is the case, how do I get the current user at the time of writing to the log?
Thanks for your help with this.
Edit
Based on the answer from Yuval, I made the following changes and it seems to work fine.
public void Process()
{
var task = CallWebService();
task.Wait();
}
public async Task CallWebService(List<Member> members)
{
using(var http = new HttpClient())
{
...
using(var result = await http.PostAsync(memberURI, new StringContent content, Encoding.UTF8, "application/json")))
{
if(result.IsSuccessStatusCode)
_status = "Success";
else
_status = "Fail";
LogEvent("Service Call - " + _status,...);
}
}
}
When I return from that call and try to call base.LogEvent(), I get an
ObjectDisposedException stating that "Safe Handle has been Closed".
That's because somewhere higher up the call-chain, someone is disposing your plugin object, which hasn't really finished the asynchronous operation. Using async void is doing a "fire and forget" operation. You don't actually await on Process, hence anyone calling it assumes it finished and disposes of your object.
Change your async void method to async Task, and await it:
public Task ProcessAsync()
{
return CallWebServiceAsync();
}
public async Task CallWebServiceAsync()
{
using (var http = new HttpClient())
{
var result = await http.PostAsync(memberURI,
new StringContent(content,
Encoding.UTF8,
"application/json"));
if (result.IsSuccessStatusCode)
_status = "Success";
else
_status = "Fail";
LogEvent("Service Call - " + _status,...);
}
}
Note you will need to await ProcessAsync somewhere higher up the call-stack as well.

Categories

Resources