I know it has been asked a lot, but my problem is, that my method won't wait for the request to be completet, even though i have implemented a TaskCompletionSource, which should have done the job, but it doesn't.
public DecksViewModel(bool local)
{
DList = new List<Deck>();
if (local)
InitializeLocalDeckList();
else
{
Dereffering();
}
}
public async void Dereffering()
{
var e = await InitilaizeWebDeckList();
List<DeckIn> decksIn = JsonConvert.DeserializeObject<List<DeckIn>>(e);
foreach (DeckIn d in decksIn)
{
Deck dadd = new Deck();
dadd.CardCount = 0;
dadd.Name = d.name;
dadd.PicturePath = d.image;
dadd.InstallDirectory = false;
DList.Add(dadd);
}
DataSource = AlphaKeyGroup<Deck>.CreateGroups(DList, System.Threading.Thread.CurrentThread.CurrentUICulture, (Deck s) => { return s.Name; }, true);
}
public Task<String> InitilaizeWebDeckList()
{
var tcs = new TaskCompletionSource<string>();
var client = new RestClient("blabla.com");
var request = new RestRequest("");
request.AddHeader("Authorization", "Basic blabla");
client.ExecuteAsync(request, response =>
{
test = response.Content;
tcs.SetResult(response.Content);
});
return tcs.Task;
}
So when I call the DecksViewModel constructor, I asyncally try to request the data from a webserver and fill the model.
The point is, that the corresponding view "doesn't wait" for the request to fill the model, so it's displayed empty.
I use the
List<AlphaKeyGroup<Deck>> DataSource
to fill a LongListSelector via DataBinding. But DataSource isn't yet set, when it is binded.
I hope you can help
You're calling an async method without awaiting it inside the constructor. That's why "it doesn't wait" (because it has nothing to wait on).
It's usually a bad idea to call an async method inside the constructor for that reason combined with the fact that constructors can't be async.
You should redesign your solution accordingly. An option is to have an async static method that creates an instance and awaits the procedure:
public static async Task CreateInstance(bool local)
{
var model = new DecksViewModel();
if (local)
{
await InitializeLocalDeckList();
}
else
{
await Dereffering();
}
}
That would allow you to not use async void which should only be used in UI even handlers.
You can read more about other options in Stephen Cleary's blog
You are using async void, which means nobody's gonna wait for that. It's just fire and forget.
I see some misunderstanding in the async keyword here:
Your code will only wait for the result of an async method, if you use await. Otherwise that call will just start the async method, but you don't know when it is actually gonna run.
You cannot use await in constructors though.
Related
This question already has answers here:
Can constructors be async?
(15 answers)
Closed 25 days ago.
I've read many explanations but none of them made sense to me.
I'm doing this in Xamarin.Forms:
public class SomeClass
{
public SomeClass
{
var request = new GeolocationRequest(GeolocationAccuracy.High, TimeSpan.FromSeconds(10));
var cts = new CancellationTokenSource();
var location = Task.Run<Location>(async () => await Geolocation.GetLocationAsync(request, cts.Token));
var userLat = location.Latitude; // doesn't work
var userLon = location.Longitude; // doesn't work
}
}
The reason I'm doing this is that I'm trying to load all the methods I need such as getting the user's location, show it on the map, load up some pins etc. as soon as the Xamarin.Forms.Maps map appears.
I know it's bad practice from what you guys answered so I'm working on changing that but I'm still having a hard time understanding how to do it differently in the sense of it's confusing. But I'm reading your articles and links to make sure I understand.
I tried to run Task.Run(async () => await) on many methods, and tried to save their values in variables but I can't get the returned value and that's what made me post this question, I need to change my code.
I know could get the returned value using Task.Result() but I've read that this was bad.
How to get the UI to load and wait on the ViewModel to do what it has to do, and then tell the UI to then use whatever the ViewModel is giving him when it is ready ?
You mentioned in a comment to another answer that you can't do it async because this code is in a constructor. In that case, it is recommended to move the asynchronous code into a separate method:
public class MyClass
{
public async Task Init()
{
var request = new GeolocationRequest(GeolocationAccuracy.High, TimeSpan.FromSeconds(10));
var cts = new CancellationTokenSource();
var location = await Geolocation.GetLocationAsync(request, cts.Token);
var userLat = location.Latitude;
var userLon = location.Longitude;
}
}
You can use it the following way:
var myObject = new MyClass();
await myObject.Init();
The other methods of this class could throw an InvalidOperationException if Init() wasn't called yet. You can set a private boolean variable wasInitialized to true at the end of the Init() method. As an alternative, you can make your constructor private an create a static method that creates your object. By this, you can assure that your object is always initialized correctly:
public class MyClass
{
private MyClass() { }
public async Task<MyClass> CreateNewMyClass()
{
var result = new MyClass();
var request = new GeolocationRequest(GeolocationAccuracy.High, TimeSpan.FromSeconds(10));
var cts = new CancellationTokenSource();
var location = await Geolocation.GetLocationAsync(request, cts.Token);
result.userLat = location.Latitude;
result.userLon = location.Longitude;
return result;
}
}
I think it could be this:
public async Task SomeMethodAsync()
{
var request = new GeolocationRequest(GeolocationAccuracy.High, TimeSpan.FromSeconds(10));
var cts = new CancellationTokenSource();
var location = await Geolocation.GetLocationAsync(request, cts.Token);
var userLat = location.Latitude;
var userLon = location.Longitude;
}
Not that methods which contain await calls should be marked as async. If you need to return something from this method then the return type will be Task<TResult>.
Constructors cannot be marked with async keyword. And it's better not to call any async methods from constructor, because it cannot be done the right way with await. As an alternative please consider Factory Method pattern. Check the following article of Stephen Cleary for details: Async OOP 2: Constructors.
The normal way to retrieve results from a Task<T> is to use await. However, since this is a constructor, there are some additional wrinkles.
Think of it this way: asynchronous code make take some time to complete, and you can never be sure how long. This is in conflict with the UI requirements; when Xamarin creates your UI, it needs something to show the user right now.
So, your UI class constructor must complete synchronously. The normal way to handle this is to (immediately and synchronously) create the UI in some kind of "loading..." state and start the asynchronous operation. Then, when the operation completes, update the UI with that data.
I discuss this in more detail in my article on async data binding.
Do the asynchronous work before constructing the object and pass in the data to the constructor. I suggest a factory.
class MyFactory : IMyFactory
{
public async Task<MyClass> GetMyClass()
{
var request = new GeolocationRequest(GeolocationAccuracy.High, TimeSpan.FromSeconds(10));
var cts = new CancellationTokenSource();
var location = Task.Run<Location>(async () => await Geolocation.GetLocationAsync(request, cts.Token));
return new MyClass(location);
}
}
class MyClass
{
public MyClass(Location location)
{
var userLat = location.Latitude;
var userLon = location.Longitude;
//etc....
}
}
To create an instance, instead of
var x = new MyClass();
you'd call
var factory = new MyFactory();
var x = await factory.GetMyClass();
The nice thing about this approach is that you can mock the factory (e.g. for unit tests) in a way that does not depend on the external service.
I am using Ninject 3.2.2.0 and I am trying to bind an async method.
This is what I got so far:
kernel.Bind<Task<MyInterface>>().ToMethod(async ctx =>
{
var someData = await DoSomethingAsync();
return new SomethingElse(someData);
}).InRequestScope();
My method DoSomethingAsync never gets called. I believe it's not being called because MyInterface is being requested, not Task<MyInterface>.
Any ideas?
The construction of your object graph should be reliable and fast. This excludes doing anything that is I/O related. This means that you should not do anything that is asynchronous at all.
Instead anything that is slow, unreliable or asynchronous should be postponed untill after the object graph is constructed.
I ended up making my binding sync
kernel.Bind<MyInterface>().ToMethod(ctx =>
{
var someData = DoSomethingSync(); // <-- Made this one sync, see method below
return new SomethingElse(someData);
}).InRequestScope();
Here is the 'magic'. I changed my method from:
private async Task<string> DoSomethingAsync() {
var result = await DoSomethingElseAsync();
return result;
}
To
private string DoSomethingSync() {
string result = null;
var runSync = Task.Factory.StartNew(async () => {
result = await DoSomethingElseAsync();
}).Unwrap();
runSync.Wait();
return result;
}
I know performance is affected because of the context switch, but at least it works as expected and you can be sure that it won't deadlock.
Please forgive me for any noobish mistakes seen below, I'm learning some of the concepts I'm attempting to work with.
Problem:
While debugging my app, I was able to call an async function with Task.Start(). I felt that the app was in a working state for the phase I'm in so removed all breakpoints with CTRL + SHIFT + F9.
Once I ran the app with no breakpoints it would fail due to a property not getting populated. Now when I try to debug any breakpoint I set in the async function that handles most of the work is longer hit. It's like it is getting skipped. Can anyone see a reason why GetWowAuctionFileInfo isn't being called?
GetWowAuctionFileInfo is what is not getting called, or at least appears to be not getting called.
Thanks.
Relevant Code
Caller Function
private void buttonTestJSFCHI_Click(object sender, RoutedEventArgs e)
{
JSON_Worker w = new JSON_Worker();
w.StartTask("FileInfo", "https://us.api.battle.net/wow/auction/data/medivh?locale=en_US&apikey=<guid>");
foreach (string res in w.ReturnedData)
{
textBoxResults.Text += res;
}
}
Called Functions
public void StartTask(string TaskName, string optionalUri= "no_uri_passed")
{
if (TaskName == "FileInfo")
{
//Need to use a lamba expression to call a delegate with a parameter
if (!(optionalUri == "no_uri_passed"))
{
Task t = new Task(() => GetWowAuctionFileInfo(optionalUri));
t.Start();
//Func<string> function = new Func<string>(() => GetWowAuctionFileInfo(optionalUri));
//Task<string> tInfo = Task<string>.Factory.StartNew(() => GetWowAuctionFileInfo(optionalUri));
}
}
}
private async void GetWowAuctionFileInfo(string auctionInfoUri)
{
RealmJSFileCheck realmInfoObject;
List<string> returnValue = new List<string>();
try
{
using (HttpClient client = new HttpClient())
{
for (int attempt = 0; attempt < 3; attempt++)
{
var response = await client.GetAsync(auctionInfoUri);
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
realmInfoObject = JsonConvert.DeserializeObject<RealmJSFileCheck>(content);
returnValue = ConvertFileInfoToConsumableList(realmInfoObject);
//returnValue = realmInfoObject.files.ToString();
break;
}
}
}
}
catch (InvalidOperationException iOpEx)
{
//recieved this when an invalid uri was passed in
}
ReturnedData = returnValue;
}
private List<string> ConvertFileInfoToConsumableList(RealmJSFileCheck jsfc)
{
List<string> returnData = new List<string>();
if (jsfc.files.Count > 0)
{
StringBuilder sb = new StringBuilder();
sb.Append("File URL: ");
sb.Append(jsfc.files[0].url);
returnData.Add(sb.ToString());
sb = new StringBuilder();
sb.AppendLine("Last Modified: ");
sb.Append(jsfc.files[0].lastModified);
returnData.Add(sb.ToString());
}
else
{
returnData.Add("No File Info Found");
}
return returnData;
}
UPDATE
Thanks again all for the detailed commentary. I've gone through much documentation regarding Task usage and learned a lot in this exercise. I'm marking the answer from #Johnathon as the solution because it provided exactly what I was asking for and provided a very helpful link for more information.
Your GetWowAuctionFileInfo method is an asynchronous method, and you await an async call within it without returning a Task. In general it is bad practice to use async void. Instead, turn your GetWowAuctionFileInfo method into async Task<List<string>> GetWowAuctionFileInfo. This will let you await the GetAsync call, parse the data, and return the collection to the caller without having to use a ReturnObject.
private async Task<List<string>> GetWowAuctionFileInfo(string auctionInfoUri)
{
RealmJSFileCheck realmInfoObject;
List<string> returnValue = new List<string>();
try
{
using (HttpClient client = new HttpClient())
{
for (int attempt = 0; attempt < 3; attempt++)
{
var response = await client.GetAsync(auctionInfoUri);
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
realmInfoObject = JsonConvert.DeserializeObject<RealmJSFileCheck>(content);
// You can just return the List<T> now.
return ConvertFileInfoToConsumableList(realmInfoObject);
//returnValue = realmInfoObject.files.ToString();
break;
}
}
}
}
catch (InvalidOperationException iOpEx)
{
//recieved this when an invalid uri was passed in
}
}
Because the method was originally async void, you could not await the calling of it in your buttonTestJSFCHI_Click. Now that we've made it all Task based, you can await it within your event handler. Note that event handlers are generally the only acceptable place to use async void. Any time you are responsible for the creation of the methods, and not constrained by a contract (like event handlers), you should always return a Task on your async methods.
private async void buttonTestJSFCHI_Click(object sender, RoutedEventArgs e)
{
JSON_Worker w = new JSON_Worker();
List<string> results = await w.StartTask("FileInfo", "https://us.api.battle.net/wow/auction/data/medivh?locale=en_US&apikey=<guid>");
foreach (string res in results)
{
textBoxResults.Text += res;
}
}
public async Task<List<string>> StartTask(string TaskName, string optionalUri= "no_uri_passed")
{
if (TaskName == "FileInfo")
{
//Need to use a lamba expression to call a delegate with a parameter
if (!(optionalUri == "no_uri_passed"))
{
// Since the GetWowAuctionFileInfo now returns Task, we don't need to create a new one. Just await the Task given back to us, and return the given result.
return await GetWowAuctionFileInfo(optionalUri);
}
}
}
The reason you saw the expected result while debugging is because the debug session was slow enough that the async operation completed in time for your code to use it. When running the app outside of the debugger, it runs faster than the async operation could complete, preventing you from seeing the data. Thus the need to await the entire async call stack, so you can prevent further execution from happening down that code-path until you receive all of the desired data.
Microsoft has a good write up on Task based programming, I'd take a read through it to help you understand it some.
EDIT
Just to clarify, when you return a Task<T> on your methods, you will be given the result when you await. For example:
List<string> result = await StartTask();
Even though StartTask returns Task<List<string>>, the await operation will wait for the StartTask() method to complete, and then unwrap the result from the Task<T> object and give you the result back automatically. So don't let the method signature fool you, if you await it, you will be given back the resulting data, and not the actual Task itself. There won't be any need for you to pull the data out of the Task manually.
Because you not waiting for result.
You loop with ReturnedData before it was assigned with data.
I think you don't need to create new Task at all. Make GetWowAuctionFileInfo method properly asynchronous which returns Task.
private async Task GetWowAuctionFileInfo(string auctionInfoUri)
{
// same code
}
Change StartTask to return Task. Because we not awaiting result here we don't need make method asynchronous.
Suggest to change name of this method to LoadData for example, which give more information about what this method does.
public Task StartTask(string TaskName, string optionalUri= "no_uri_passed")
{
if (TaskName == "FileInfo")
{
//Need to use a lamba expression to call a delegate with a parameter
if (!(optionalUri == "no_uri_passed"))
{
return GetWowAuctionFileInfo(optionalUri) // this will return Task
}
}
// if validation fails - return completed task or throw exception
return Task.CompletedTask;
}
Then you can call it in Button_Click event handler
private async void buttonTestJSFCHI_Click(object sender, RoutedEventArgs e)
{
JSON_Worker w = new JSON_Worker();
await w.StartTask("FileInfo", "yourUrl");
// This line will be executed only after asynchronous methods completes succesfully
// or exception will be thrown
foreach (string res in w.ReturnedData)
{
textBoxResults.Text += res;
}
}
For the following code im calling that async void method.
public void LoadSystemDetails(int clientId)
{
DataSystem.Name = "Salesforce System";
DataSystem.Description = "";
GetAllFields(clientId);
}
following code is GetAllFields method
public async void GetAllFields(int clientId)
{
this.DataSystem.SystemTables = new List<DataSystemTable>();
using (var forceClient = await ConnectToSalesforceByOAuth(clientId))
{
var SalesForceTable = new DataSystemTable
{
TableName = "Contact"
};
DataSystem.SystemTables.Add(SalesForceTable);
var contact = forceClient.DescribeAsync<SalesforceObject>("Contact");
var tableFields = new List<DataSystemField>();
foreach (var con in contact.Result.Fields)
{
tableFields.Add(new DataSystemField
{
ColumnName = con.Name,
});
}
SalesForceTable.EissSyncSystemFields = tableFields;
}
and i call callbackScript as below.
callbackScript.AppendLine(string.Format("var destinationSystem ={0};", JsonConvert.SerializeObject(this.DestinationSystem, Formatting.Indented)));
here DestinationSystem is calling the LoadSystemDetails. Like DestinationSystem.LoadSystemDetails(clientId)
while using (var forceClient = await ConnectToSalesforceByOAuth(clientId)) line is execute at the time the callbackScript is executed. so the SystemTables doesn't have any value. but it having Name and Description.
so here i need to wait the LoadSystemDetails to finish the GetAllFields.
How i do that.Please help me.
Thanks.
If you need LoadSystemDetails to wait for GetAllFields, there are 2 problems here:
you are calling async method from a synchronous context
GetAllFields is async void, which means fire and forget. You will never be able to wait for it to finish.
Solution:
First, NEVER use async void if you need to wait for the result or end. Use async Task instead
Second, either convert LoadSystemDetails to async method also, then await the GetAllFields (that should return Task), or use GetAllFields(clientId).Wait()
take a look at this article for more information on async/await: https://msdn.microsoft.com/en-us/magazine/jj991977.aspx
As #netchkin already said: Never use async void. Use async Task. You don't have to return a Task yourself, you can still use return;
I'm building a Metro App.
In the MainPage.xaml.cs, I instantiate Album as follows:
Album album = new Album(2012); //With the album ID as its parameter.
ListView1.ItemsSource = album.Songs;
In the Album.cs, the constructor is as follows:
public Album(int ID)
{
this.ID = ID;
Initialize(); //Serves as a wrapper because I have to call httpClient.GetStreamAsync() and "async" doesn't work for the constructor.
}
Finally, the Initialize method:
private async void Initialize()
{
//...some code...
HttpClient cli = new HttpClient();
Stream SourceStream = await HttpClient.GetStreamAsync("http://contoso.com");
//...some code...
this.Songs = Parse(SourceStream);
}
The problem is when it runs to GetStreamAsync, it then goes to ListView1.ItemsSource = album.Songs directly with the album.Songs null.
Is there any quick solution to this problem?
Yes. The whole point of async and await are that you don't block. Instead, if you're "awaiting" an operation which hasn't completed yet, a continuation is scheduled to execute the rest of the async method, and control is returned to the caller.
Now because your method has a type of void, you have no way of knowing when that's even finished - if you returned Task (which wouldn't require any change in the body of the method) you'd at least be able to work out when it had finished.
It's not really clear what your code looks like, but fundamentally you should only be trying to set the ItemsSource after initialization has finished. You should probably have your MainPage code in an async method too, which would look something like:
Album album = new Album(2012);
ListView1.ItemsSource = await album.GetSongsAsync();
Your GetSongs() call would then be:
private async Task<List<Song>> GetSongsAsync()
{
//...some code...
HttpClient cli = new HttpClient();
Stream SourceStream = await HttpClient.GetStreamAsync("http://contoso.com");
//...some code...
return Parse(SourceStream);
}
This means Songs would no longer be a property of Album itself, although you could add it in for caching purposes if you wanted to.
Make Songs property return Task<List<Song>> and await at ListView1.ItemsSource = await album.Songs;