I'm testing the waters of Xamarin and trying to convert an android project I have to it. I have a very basic login using facebook. My code looks like this:
public class MainActivity : Activity, IFacebookCallback
{
private LoginButton loginButton;
ICallbackManager callbackManager;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.Main);
loginButton = FindViewById<LoginButton>(Resource.Id.login_button);
loginButton.SetReadPermissions(new List<string>() {"email"});
this.callbackManager = CallbackManagerFactory.Create();
loginButton.RegisterCallback(callbackManager, this);
}
public async void OnSuccess(Java.Lang.Object result)
{
var loginResult = result as LoginResult;
var accesstoken = loginResult.AccessToken;
var client = new FacebookApiClient();
var textview = FindViewById<TextView>(Resource.Id.emailDisplay);
try
{
var fbResponse = await GetFaceBookResponse(accesstoken);
textview.Text = fbResponse.Email;
}
catch(System.Exception ex)
{
var myException = ex;
}
}
private async Task<FacebookResponse> GetFaceBookResponse(AccessToken accessToken)
{
var client = new FacebookApiClient();
var response = await client.GetEmail(accessToken);
return response;
}
}
So I'm implementing the OnSuccess method in IFacebookCallback, which requires a void method. This appears to be the only way to do this, which seems ok to me and also appears to be working fine. However, I know it's a no-no to use async with a void return type, except in special situations (event handlers). Just double checking here to make sure this won't cause any problems?
Also, if there is a better way to do this, what is it? While I was googling I found a way using Task.Run:
var cts = new CancellationTokenSource();
var ct = cts.Token;
try
{
var fbResponse = await Task.Run(async () =>
{
var response = await client.GetEmail(accesstoken);
return response;
}, ct);
textview.Text = fbResponse.Email;
}
But I'm not really sure why that would be better than what I'm doing, or if it's actually really any different.
So I'm implementing the OnSuccess method in IFacebookCallback, which requires a void method. This appears to be the only way to do this, which seems ok to me and also appears to be working fine. However, I know it's a no-no to use async with a void return type, except in special situations (event handlers). Just double checking here to make sure this won't cause any problems?
That's exactly what you're using it for, so it's fine. As long as you're not trying to await that method anywhere in your own code (since async void methods can't be awaited, which is the reason why they're a no-no), you're good.
One of Microsoft's samples also contains a simple async implementation of IFacebookCallback.OnSuccess(), here.
Related
I'm trying to send a HTTP POST request multiple times but not in parallel. I've created another class and put this function into it:
public async Task<string> sendReqAsync()
{
requestTime = DateTime.Now;
var task = await client.PostAsync("https://api.example.com", content).ContinueWith(async (result) =>
{
responseTime = DateTime.Now;
return await result.Result.Content.ReadAsStringAsync();
});
return task.Result;
}
And finally it'll be called in an click event for a button:
private async void ManualSendBtn_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
// Here "client" is object of my class that have been initilized in the main WPF function
await client.sendReqAsync().ContinueWith((result) =>
{
var parsedResponse = JObject.Parse(result.Result);
this.Dispatcher.Invoke(() =>
{
ordersListView.Items.Add(new OrderDetailsItem
{
IsSuccessful = bool.Parse(parsedResponse["IsSuccessfull"].ToString()),
ServerMessage = parsedResponse["MessageDesc"].ToString(),
RequestTime = client.requestTime.ToString("hh:mm:ss.fff"),
ResponseTime = client.responseTime.ToString("hh:mm:ss.fff"),
ServerLatency = (client.responseTime - client.requestTime).Milliseconds.ToString()
});
});
});
}
But it works just for the first click (event occurs) and for next click it gives me:
Inner Exception 1: AggregateException: One or more errors occurred.
Inner Exception 2: AggregateException: One or more errors occurred.
Inner Exception 3: ObjectDisposedException: Cannot access a disposed
object.
Edit: Below is the edited code as suggested, but the error is not fixed.
MyHTTPClient
public class MyHTTPClient
{
private static readonly HttpClient client = new HttpClient();
StringContent content;
public DateTime requestTime;
public DateTime responseTime;
public void configureReq(string oauthToken)
{
var postBodyJSONObject = new
{
body = "value"
};
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", oauthToken);
content = new StringContent(JsonConvert.SerializeObject(postBodyJSONObject), Encoding.UTF8, "application/json");
}
public async Task<string> sendReqAsync()
{
requestTime = DateTime.Now;
var result = await client.PostAsync("https://example.com", content);
responseTime = DateTime.Now;
return await result.Content.ReadAsStringAsync();
}
}
Also here is my window initializer function:
MyHTTPClient client; // Somewhere in the class
public MainWindow() {
client = new MyHTTPClient();
client.configureReq(AUTHORIZATION);
}
First of all: You shouldn't mix different asynchronous models of programming.
In your code you are trying to combine async/await with legacy ContinueWith approach.
You can read more about async/await approach here and about async/await and ContinueWith differences here (Thanks to MickyD for leaving link to this in the comments).
So to cut a long story short your code should look like this:
public async Task<string> SendRequestAsync()
{
requestTime = DateTime.Now;
var response = await client.PostAsync("api.example.com", content);
var responseTime = DateTime.Now;
return await response.Content.ReadAsStringAsync();
}
Note that now code looks much more like synchronous and is a way readable than callbacks with ContinueWith.
You can find more about asynchronous programming in C# on MSDN. I suggest you to read and try to exectue some code in console app before writing it in yourmain app.
In addition to fixing the SendRequestAsync method, as shown by Neistow's answer, you must fix the button's click handler too:
private async void ManualSendBtn_PreviewMouseLeftButtonUp(object sender,
MouseButtonEventArgs e)
{
string response = await client.SendRequestAsync();
var parsedResponse = JObject.Parse(response);
ordersListView.Items.Add(new OrderDetailsItem
{
IsSuccessful = bool.Parse(parsedResponse["IsSuccessfull"].ToString()),
ServerMessage = parsedResponse["MessageDesc"].ToString(),
RequestTime = client.RequestTime.ToString("hh:mm:ss.fff"),
ResponseTime = client.ResponseTime.ToString("hh:mm:ss.fff"),
ServerLatency = (client.ResponseTime - client.RequestTime).Milliseconds.ToString()
});
}
There is no reason to mess with the Dispatcher.Invoke. The code after the await will continue running on the UI thread, as it happens by default in all Windows Forms and WPF applications.
Reading Stephen Cleary take on not blocking on Async code I write something like this
public static async Task<JObject> GetJsonAsync(Uri uri)
{
using (var client = new HttpClient())
{
var jsonString = await client.GetStringAsync(uri).ConfigureAwait(false);
return JObject.Parse(jsonString);
}
}
public async void Button1_Click(...)
{
var json = await GetJsonAsync(...);
textBox1.Text=json;
}
so far so good, I understand that after the ConfigureAwait the method is going to continue running on a different context after GetStringAsync returns.
but what about if I want to use something like MessageBox (which is UI) like this
public static async Task<JObject> GetJsonAsync(Uri uri)
{
if(someValue<MAXVALUE)
{
using (var client = new HttpClient())
{
//var jsonString = await client.GetStringAsync(uri); //starts the REST request
var jsonString = await client.GetStringAsync(uri).ConfigureAwait(false);
return JObject.Parse(jsonString);
}
}
else
{
MessageBox.Show("The parameter someValue is too big!");
}
}
can I do this?
Even more complicated, how about this?
public static async Task<JObject> GetJsonAsync(Uri uri)
{
if(someValue<MAXVALUE)
{
try{
using (var client = new HttpClient())
{
//var jsonString = await client.GetStringAsync(uri); //starts the REST request
var jsonString = await client.GetStringAsync(uri).ConfigureAwait(false);
return JObject.Parse(jsonString);
}
}
catch(Exception ex)
{
MessageBox.Show("An Exception was raised!");
}
}
else
{
MessageBox.Show("The parameter someValue is too big!");
}
}
Can I do this?
Now, I am thinking perhaps all the message boxes should be called outside GetJsonAync as good design, but my question is can the above thing be done?
can I do this? [use a MessageBox]
Yes, but mainly because it has nothing to do with async/await or threading.
MessageBox.Show() is special, it is a static method and is documented as thread-safe.
You can show a MessageBox from any thread, any time.
So maybe it was the wrong example, but you do have MessageBox in the title.
public static async Task<JObject> GetJsonAsync(Uri uri)
{
try{
... // old context
... await client.GetStringAsync(uri).ConfigureAwait(false);
... // new context
}
catch
{
// this might bomb
someLabel.Text = "An Exception was raised!";
}
}
In this example, there could be code paths where the catch runs on the old and other paths where it runs on the new context.
Bottom line is: you don't know and should assume the worst case.
I would not use a Message Box, as it is very limited, and dated.
Also, Pop up's are annoying.
Use your own user control which enables user interaction the way you intend it.
In the context of Winforms / WPF / (and I guess UWP), only a single thread can manipulate the UI. Other threads can issue work to it via a queue of actions which eventually get invoked.
This architecture prevents other threads from constantly poking at the UI, which can make UX very janky (and thread unsafe).
The only way to communicate with it the UI work queue (in Winforms) is via the System.Windows.Form.Controls.BeginInvoke instance method, found on every form and control.
In your case:
public async void Button1_Click(...)
{
var json = await GetJsonAsync(...).ConfigureAwait(false);
BeginInvoke(UpdateTextBox, json);
}
private void UpdateTextBox(string value)
{
textBox1.Text=json;
}
I am running crazy with using the HttpClient in C#...
I simplified my project so my problem can be replicated easier. All i want to do is calling HttpClient.PostAsync in the Background without blocking my UI Window (I am using WPF btw).
Here is my code (slimed the code to the min.):
Bing is only used here to not show my private webservice it can be replaced with every other website of course.
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
try {
MyTextBlock.Text = "Waiting...";
Uri webUri = new Uri("https://www.bing.com/");
using (HttpClient client = new HttpClient()) {
using (HttpResponseMessage response = await client.PostAsync(webUri, new MultipartFormDataContent())) {
MyTextBlock.Text = await response.Content.ReadAsStringAsync();
}
}
} catch (Exception exc) {
MessageBox.Show(exc.ToString(), "Unhandled Exception");
}
}
While my UI is waiting for the async post request it shows "Waiting" in a TextBox. And when the Async Request returns it shows the result. Nothing more should happen.
So here the Problem occurs, sometimes the PostAsync Method simply doesn't return... Even the Timeout is ignored. When I am debugging it always works but when I try the start the application it somettimes hangs. Not always which is not making find the error easier. I tried many ways with calling the request async but every time the same issue.
I also read following blog with the blocking issue in async methods but even with ConfigureAwait no differnce.
http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
I just can imagine that there is a problem within the HttpClient async method locking the main thread, so it cause this problem. Wenn i use the same code in a ConsoleApplication everything is fine. There is a proxy between my client and the destination but that shouldn't be a problem at all.
Can someone replicate this problem? I am using C#/WPF with .NET Framework 4.6.1.
You don't need to await client.PostAsync(webUri, i_formData) because you don't do anything with the result after the call returns, you can just return the Task. Change to this;
public static Task<HttpResponseMessage> BasicRequest(MultipartFormDataContent i_formData)
{
Uri webUri = new Uri("https://www.bing.com");
HttpClient client = new HttpClient {
Timeout = TimeSpan.FromSeconds(1)
};
return client.PostAsync(webUri, i_formData);
}
Your Window_Load is an event handler. You can make it async void, which is the only time you don't have to return Task. By making it async, you can remove all the over complicated code:
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
MyTextBlock.Text = "Waiting";
HttpResponseMessage response = await BasicRequest(new
MultipartFormDataContent());
string test = await response.Content.ReadAsStringAsync();
this.Close();
}
First at all thanks for your help, the problem seems to be solved now.
I had to do 3 things to get this work:
Get a instance of HttpClient of use it the whole application life time, so no using anymore for HttpClient.
Don't call PostAsync in the Window_Loaded Event, it seems to be to early sometimes. (I still don't get why...)
Don't use ConfigureAwait(false)
The code now looks something like this:
HttpClient client = new HttpClient();
private async void MyButton_Click(object sender, RoutedEventArgs e)
{
try {
MyTextBlock.Text = "Waiting...";
Uri webUri = new Uri("https://www.bing.com/");
using (HttpResponseMessage response = await client.PostAsync(webUri, new ipartFormDataContent())) {
MyTextBlock.Text = await response.Content.ReadAsStringAsync();
}
} catch (Exception exc) {
MessageBox.Show(exc.ToString(), "Unhandled Exception");
}
}
And to get this at start up done i had to make a really bad piece of good. But it works finally:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
DispatcherTimer startupTimer = new DispatcherTimer();
startupTimer.Tick += new EventHandler((o, a) => {
MyFunction();
startupTimer.Stop();
});
startupTimer.Interval = TimeSpan.FromSeconds(1);
startupTimer.Start();
}
When someone can replicate these behavior or can explain why these was happening, please comment it here :)
Update
Issue still occurs but it seems to be only there when the client is using some kind of proxy!
I have the following code to connect to MYOB's SDK
var cfsCloud = new CompanyFileService(_configurationCloud, null, _oAuthKeyService);
cfsCloud.GetRange(OnComplete, OnError);
where
private void OnComplete(HttpStatusCode statusCode, CompanyFile[] companyFiles)
{ // ask for credentials etc }
I want to convert this to use a TaskCompletionSource
like this example
however my OnComplete has multiple parameters.
How do I code that?
As mentioned in the comment
The SDK for Accountright API supports async/await i.e. GetRangeAsync
so you can do something like this if you wanted/needed to wrap it in a TaskCompletionSource
static Task<CompanyFile[]> DoWork()
{
var tcs = new TaskCompletionSource<CompanyFile[]>();
Task.Run(async () =>
{
var cfsCloud = new CompanyFileService(_configurationCloud, null, _oAuthKeyService);
var files = await cfsCloud.GetRangeAsync();
tcs.SetResult(files);
});
return tcs.Task;
}
I have created a webservice and a method as follows:
[WebMethod]
public bool GetMasterExit(int RoomID)
{
if(GameList[RoomID][0] == "\0")
return true;
else
return false;
}
And then I call this from WP client using the following way but there is a problem that I want the main process blocked until the webservice returns the value I want but in this way the value is wrong for delay.
I have tried to use the Await but I got an error that "cannot await void", so Anyone knows how to solve this problem?
public void Test()
{
ServiceSoapClient GameClient = new ServiceSoapClient();
GameClient.GetMasterExitCompleted += _clientGetMasterExitCompleted;
GameClient.GetMasterExitAsync(RoomID);
Console.WriteLine(MasterExit);
}
public void _clientGetMasterExitCompleted(object sender, GetMasterExitCompletedEventArgs e)
{
MasterExit = e.Result;
}
I want the main process blocked until the webservice returns the value
This is an incorrect approach. Especially on mobile platforms such as WP, you're not allowed to block the UI like that.
The proper solution is to use await. Follow the Task-based Asynchronous Pattern and write an EAP wrapper called GetMasterExitTaskAsync.
If you are willing to use await then you must add modifier async to your function signature.Hope this helps.
Use async and await like this:
public async void Test()
{
ServiceSoapClient GameClient = new ServiceSoapClient();
GameClient.GetMasterExitCompleted += _clientGetMasterExitCompleted;
await GameClient.GetMasterExitAsync(RoomID);
Console.WriteLine(MasterExit);
}