Is it possible to block a function call that returns a future?
I was under the impression calling .then() does it, but that's not what I'm seeing in my output.
print("1");
HttpRequest.getString(url).then((json) {
print("2");
});
print("3");
What I'm seeing in my output is:
1
3
2
The getString method doesn't have an async that would allow me to await it and then executes asynchronously in any case.
static Future<String> getString(String url,
{bool withCredentials, void onProgress(ProgressEvent e)}) {
return request(url, withCredentials: withCredentials,
onProgress: onProgress).then((HttpRequest xhr) => xhr.responseText);
}
How do I make it blocking without placing an infinite while loop before step 3 waiting for step 2 to be completed (not that it would work anyways due to the single thread nature of Dart)?
The above HttpRequest loads a config.json file that determines how everything works in the app, if the request for a field in the config is done before the config.json file is done loading, it causes errors, so I need to wait until the file is done loading before I allow calling getters on the fields of the class or getters needs to wait for the once-off loading of the config.json file.
Update, this is what I eventually did to make it work after Günter suggested I use a Completer:
#Injectable()
class ConfigService {
Completer _api = new Completer();
Completer _version = new Completer();
ConfigService() {
String jsonURI =
"json/config-" + Uri.base.host.replaceAll("\.", "-") + ".json";
HttpRequest.getString(jsonURI).then((json) {
var config = JSON.decode(json);
this._api.complete(config["api"]);
this._version.complete(config["version"]);
});
}
Future<String> get api {
return this._api.future;
}
Future<String> get version {
return this._version.future;
}
}
And where I use the ConfigService:
#override
ngAfterContentInit() async {
var api = await config.api;
var version = await config.version;
print(api);
print(version);
}
Now I get blocking-like functionality without it actually blocking.
There is no way to block execution until asynchronous code completes. What you can do is to chain successive code so that it is not executed before the async code is completed.
One way to chain is then
print("1");
HttpRequest.getString(url) // async call that returns a `Future`
.then((json) { // uses the `Future` to chain `(json) { print("2"); }`
print("2");
});
print("3"); // not chained and therefore executed before the `Future` of `getString()` completes.
An async call is just scheduling code for later execution. It will be added to the event queue and when the tasks before it are processed it itself will be executed. After an async call is scheduled the sync code `print("3") is continued.
In your case HttpRequest.getString() schedules a call to your server and registers (json) { print("2") as callback to be called when the response from the server arrives. Further execution of the application doesn't stall until the response arrives and there is no way to make that happen. What instead happens is that sync code is continued to be executed (print("3")).
If your currently executed sync code reaches its end, then the next scheduled task is processed the same way.
then() schedules the code (json) { print("2"); } to be executed after getString() completed.
await
async and await just make async code look more like sync code but otherwise it is quite the same and will be translated under the hood to xxx.then((y) { ... }).
I'm not sure I understand what you're trying to achieve, but it looks to me you want to do this:
myFunction() async {
print("1");
final json = await HttpRequest.getString(url);
print("2");
print("3");
}
async statement is only needed in the consumer function. In other words, producer functions doesn't need to have async, they only need to return a Future.
you should be able to do this:
Future consumerFunc() async {
print("1");
var response = await HttpRequest.getString(url);
print("2");
print("3");
}
and it should result:
1
2
3
Note: await replaces then methods
Related
I have two ways of retrieving secrets from my vault. One is async while the other is not.
Async:
public static async Task<string> GetSecret(string secretName)
{
try
{
return (await GetClient().GetSecretAsync("VaultURL", secretName)).Value;
}
catch (Exception ex)
{
return ex.Message;
}
}
Not async:
public static string GetSecretWithoutAwait(string SecretName)
{
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
return keyVaultClient.GetSecretAsync("vaultUrl", SecretName).Result.Value;
}
Not having an async method suits my needs the most, and I prefer to keep it that way if possible. What are the consequences to making the retrieval a non-async process?
What are the consequences to making the retrieval a non-async process?
The Result member only exists on the Task<T> type; it does not exist on the Task type.
Like Wait, Result will synchronously block the calling thread until the task completes. This is generally not a good idea for the same reason it wasn’t a good idea for Wait: it’s easy to cause deadlocks.
If you have to block waiting the completion of an Async Task, use GetAwaiter().GetResult(). Wait and Result will wrap any exceptions within an AggregateException, which complicates error handling. The advantage of GetAwaiter().GetResult() is that it returns a normal exception instead of an AggregateException.
So I suggest that you use Async method to retrieve secrets
I need to repeatedly call an asynchronous function in Dart, let's call it expensiveFunction, for a variable number of arguments. However, since each call is quite memory consuming, I cannot afford to run them in parallel. How do I force them to run in sequence?
I have tried this:
argList.forEach( await (int arg) async {
Completer c = new Completer();
expensiveFunction(arg).then( (result) {
// do something with the result
c.complete();
});
return c.future;
});
but it hasn't had the intended effect. The expensiveFunction is still being called in parallel for each arg in argList. What I actually need is to wait in the forEach loop until the expensiveFunction completes and only then to proceed with the next element in the argList. How can I achieve that?
You'll want to use a classic for loop here:
doThings() async {
for (var arg in argList) {
await expensiveFunction(arg).then((result) => ...);
}
}
There are some nice examples on the language tour.
Why is the print result keep returning "asd"?
var jerl = "asd";
HttpRequest.request('Foo',
method: "GET",
requestHeaders: {"Authorization": "Secret"}).then((x) => x.responseText).then((y) => jerl = y);
print(jerl);
Async code is just scheduled for later execution and the sync code continues executing without waiting for the async code. The method you pass to Future.then(...) is executed when the scheduled async code is finished. You find a lot of such questions and examples tagged [:dart-async:] here on StackOverflow.
If you want code to be executed when the async code completed you need to chain it with then like you did with other code
var jerl = "asd";
HttpRequest.request('Foo',
method: "GET",
/* return */ requestHeaders: {"Authorization": "Secret"})
.then((response) {
var jer1 = x.responseText;
print(jerl);
});
You can use the async/await feature to make code look more like sync code
Future someMethod() async { // <== added `Future` and `async`
var jerl = "asd";
var response = await HttpRequest.request('Foo', // <== added `await`
method: "GET",
requestHeaders: {"Authorization": "Secret"}).then((x) =>
print(response.text);
}
async is contagious. There is no way to go back from async execution to sync execution. If your code calls any async method or function and you need code further down to wait for the async result, then you need to chain all following code (also in calling methods) with .then (or use async/await). For this to work you also need to return the future to the caller so he can call .then() on it.
With async/await you don't need to explicitly return the future.
async/await doesn't make async code sync (as mentioned above), it only makes it look more like sync code. The underlying behavior is identical as with then.
Is it possible to encapsulate repeated send/responses to the same dart isolate within a single asynchronous function?
Background:
In order to design a convenient API, I would like to have a function asynchronously return the result generated by an isolate, e.g.
var ans = await askIsolate(isolateArgs);
This works fine if I directly use the response generated by a spawnUri call, e.g
Future<String> askIsolate(Map<String,dynamic> isolateArgs) {
ReceivePort response = new ReceivePort();
var uri = Uri.parse(ISOLATE_URI);
Future<Isolate> remote = Isolate.spawnUri(uri, [JSON.encode(isolateArgs)], response.sendPort);
return remote.then((i) => response.first)
.catchError((e) { print("Failed to spawn isolate"); })
.then((msg) => msg.toString());
}
The downside of the above approach, however, is that if I need to repeatedly call askIsolate, the isolate must be spawned each time.
I would instead like to communicate with a running isolate, which is certainly possible by having the isolate return a sendPort to the caller. But I believe since the 2013 Isolate refactoring , this requires the caller to listen to subsequent messages on the receivePort, making encapsulation within a single async function impossible.
Is there some mechanism to accomplish this that I'm missing?
The answer depend on how you intend to use the isolate
Do you intend to keep it running indefinitely, sending it inputs and expecting to receive answers asynchronously?
Do you want to send the isolate many (but finite) inputs at once, expect to receive answers asynchronously, then close the isolate?
I'm guessing the latter, and your askIsolate() function needs to immediately return a Future than completes when it receives all the answers.
The await for loop can be used to listen to a stream and consume events until it closes.
I'm not familiar with isolates, so I hope this is OK, I have not tested it. I've assumed that the isolate terminates and response closes.
String askIsolate(Map<String,dynamic> isolateArgs) async {
ReceivePort response = new ReceivePort();
var uri = Uri.parse(ISOLATE_URI);
Isolate.spawnUri(uri, [JSON.encode(isolateArgs)], response.sendPort)
.catchError((e)) {
throw ...;
});
List<String> answers = new List<String>;
await for(var answer in response) {
out.add(answer.toString());
}
return answers;
}
Note:
response is the stream you are listening to for answers. It's created before spawning the isolate so you don't need to (and probably should not) wait for the isolate future to complete before listening to it.
I made askIsolate() async because that makes it very easy to immediately return a future which completes when the function returns - without all that tedious mucking about with .then(...) chains, which I personally find confusing and hard to read.
BTW, your original then(...).catchError(...) style code would be better written like this:
Isolate.spawnUri(uri, [JSON.encode(isolateArgs)], response.sendPort)
.catchError((e) { ... });
return response.first)
.then((msg) => msg.toString());
I believe that delaying attaching a catchError handler to the line after the isolate's creation might allow the future to complete with an error before the handler is in place.
See: https://www.dartlang.org/articles/futures-and-error-handling/#potential-problem-failing-to-register-error-handlers-early .
I also recommend looking at IsolateRunner in package:isolate, it is intended to solve problems like this - calling a function in the same isolate several times instead of just once when the isolate is created.
If you don't want that, there are other, more primitive, options
Async functions can wait on futures or streams and a ReceivePort is a stream.
For a quick hack, you can probably do something with an await for on the response stream, but it won't be very convenient.
Wrapping the ReceivePort in a StreamQueue from package:async is a better choice. That allows you to convert the individual events into futures. Something like:
myFunc() async {
var responses = new ReceivePort();
var queue = new StreamQueue(responses);
// queryFunction sends its own SendPort on the port you pass to it.
var isolate = await isolate.spawn(queryFunction, [], responses.sendPort);
var queryPort = await queue.next();
for (var something in somethingToDo) {
queryPort.send(something);
var response = await queue.next();
doSomethingWithIt(response);
}
queryPort.send("shutdown command");
// or isolate.kill(), but it's better to shut down cleanly.
responses.close(); // Don't forget to close the receive port.
}
A quick working example based on lrn's comment above follows. The example initializes an isolate via spawnURI, and then communicates with the isolate by passing a new ReceivePort upon which a reply is expected. This allows the askIsolate to directly return a response from a running spawnURI isolate.
Note error handling has been omitted for clarity.
Isolate code:
import 'dart:isolate';
import 'dart:convert' show JSON;
main(List<String> initArgs, SendPort replyTo) async {
ReceivePort receivePort = new ReceivePort();
replyTo.send(receivePort.sendPort);
receivePort.listen((List<dynamic> callArgs) async {
SendPort thisResponsePort = callArgs.removeLast(); //last arg must be the offered sendport
thisResponsePort.send("Map values: " + JSON.decode(callArgs[0]).values.join(","));
});
}
Calling code:
import 'dart:async';
import 'dart:isolate';
import 'dart:convert';
const String ISOLATE_URI = "http://localhost/isolates/test_iso.dart";
SendPort isolateSendPort = null;
Future<SendPort> initIsolate(Uri uri) async {
ReceivePort response = new ReceivePort();
await Isolate.spawnUri(uri, [], response.sendPort, errorsAreFatal: true);
print("Isolate spawned from $ISOLATE_URI");
return await response.first;
}
Future<dynamic> askIsolate(Map<String,String> args) async {
if (isolateSendPort == null) {
print("ERROR: Isolate has not yet been spawned");
isolateSendPort = await initIsolate(Uri.parse(ISOLATE_URI)); //try again
}
//Send args to the isolate, along with a receiveport upon which we listen for first response
ReceivePort response = new ReceivePort();
isolateSendPort.send([JSON.encode(args), response.sendPort]);
return await response.first;
}
main() async {
isolateSendPort = await initIsolate(Uri.parse(ISOLATE_URI));
askIsolate({ 'foo':'bar', 'biz':'baz'}).then(print);
askIsolate({ 'zab':'zib', 'rab':'oof'}).then(print);
askIsolate({ 'One':'Thanks', 'Two':'lrn'}).then(print);
}
Output
Isolate spawned from http://localhost/isolates/test_iso.dart
Map values: bar,baz
Map values: zib,oof
Map values: Thanks,lrn
I am trying to write a multi threading search and then display all the results once the tasks have finished running but currently I don't understand how to process the results once all the tasks are complete
My code is as follows:
private async void DoSearchAsync()
{
var productResults = await SearchProductsAsync(CoreCache.AllProducts);
var brochureResults = await SearchBrochuresAsync(CoreCache.AllBrochures);
_searchResults.AddRange(productResults);
_searchResults.AddRange(brochureResults);
ResultsCount = _searchResults.Count;
}
Where _searchResults is a List<SearchResult>
My understanding is that it will do both of the awaits simultaneously and then add the products to the search results. However when I call this in my controller:
public ActionResult Index(string searchText)
{
SearchHelper helper = new SearchHelper(searchText);
helper.DoSearchAsync();
return View(helper);
}
It is displaying the page before the searches are complete so no results are showing up. How do I make it wait for the results to finish before showing the page?
I've had a look into Tasks.Wait but don't know how to apply it to the above as it expects an array of tasks
private Task<List<SearchResult>> SearchProductsAsync(IEnumerable<Product> products)
{
return Task<List<SearchResult>>.Factory.StartNew(() => GetProducts(products));
}
private Task<List<SearchResult>> SearchBrochuresAsync(IEnumerable<Assets> brochures)
{
return Task<List<SearchResult>>.Factory.StartNew(() => GetBrochures(brochures));
}
Every time you call Factory.StartNew or Task.Run inside an ASP.NET controller, you grab a thread from ThreadPool. That thread could be otherwise serving another incoming HTTP request. So, your're really hurting your web app scalability. This may be a serious issue, depending on how many concurrent HTTP requests your web app is expected to receive.
Are you OK with that? If so, the code could look like this:
private async Task DoSearchAsync()
{
var productResults = SearchProductsAsync(CoreCache.AllProducts);
var brochureResults = SearchBrochuresAsync(CoreCache.AllBrochures);
await Task.WhenAll(productResults, brochureResults);
_searchResults.AddRange(productResults.Result);
_searchResults.AddRange(brochureResultsbrochure.Results);
ResultsCount = _searchResults.Count;
}
public async Task<ActionResult> Index(string searchText)
{
SearchHelper helper = new SearchHelper(searchText);
await helper.DoSearchAsync();
return View(helper);
}
Note I changed async void to async Task for DoSearchAsync, and made your controller method async, so it returns Task<ActionResult>.
My understanding is that it will do both of the awaits simultaneously
This is not correct Pete. The await means that it will pause execution of the current method until such time that the Async method being called completes. So your 2 searches will not run in Parallel.
For clarification, see the first line of the MSDN documentation....
The await operator is applied to a task in an asynchronous method to suspend the execution of the method until the awaited task completes.
You should use Task.WhenAll to wait for both searches to complete. As WhenAll supports returning Task<T> and both of your Async searches return a List<SearchResult> you can combine the results of both searches and await them both in one statement...
var searchProducts = Task.Factory.StartNew(() => GetProducts(CoreCache.AllProducts));
var searchBrochure = Task.Factory.StartNew(() => GetBrochures(CoreCache.AllBrochures));
var allResults = await Task.WhenAll(new [] { searchProducts, searchBrochure });
//allResults is List<SearchResult>[]
For completeness, its worth noting that allResults will contain 2 result sets. It does not perform a union on the 2 sets.
A complete working example for LinqPad can be found here on GitHub
You can use a task factory, then call wait on the resulting task:
Task taskA = Task.Factory.StartNew(() => DoSomeWork(10000000));
taskA.Wait();
Console.WriteLine("taskA has completed.");
http://msdn.microsoft.com/en-us/library/dd537610(v=vs.110).aspx
You can make it wait for the results to finish before displaying the page by making your function NOT be async.
Or if you want async you could move the search functions to be called by an AJAX call triggered when your page is loaded
You need to wait for the function to complete. In order to do that, first make it return a task:
private async Task DoSearchAsync()
then await it:
public async Task<ActionResult> Index(string searchText)
{
SearchHelper helper = new SearchHelper(searchText);
await helper.DoSearchAsync();
return View(helper);
}
For more information, see http://www.asp.net/mvc/tutorials/mvc-4/using-asynchronous-methods-in-aspnet-mvc-4