How do I merge a List<T> of TPL-based tasks for later execution?
public async IEnumerable<Task<string>> CreateTasks(){ /* stuff*/ }
My assumption is .Concat() ...
void MainTestApp() // Full sample available upon request.
{
List<string> nothingList = new List<string>();
nothingList.Add("whatever");
cts = new CancellationTokenSource();
delayedExecution =
from str in nothingList
select AccessTheWebAsync("", cts.Token);
delayedExecution2 =
from str in nothingList
select AccessTheWebAsync("1", cts.Token);
delayedExecution = delayedExecution.Concat(delayedExecution2);
}
/// SNIP
async Task AccessTheWebAsync(string nothing, CancellationToken ct)
{
// return a Task
}
I want to make sure that this won't spawn any task or evaluate anything. In fact, I suppose I'm asking "what logically executes an IQueryable to something that returns data"?
Background
Since I'm doing recursion and I don't want to execute this until the correct time, what is the correct way to merge the results if called multiple times?
If it matters I'm thinking of running this command to launch all the tasks var AllRunningDataTasks = results.ToList(); followed by this code:
while (AllRunningDataTasks.Count > 0)
{
// Identify the first task that completes.
Task<TableResult> firstFinishedTask = await Task.WhenAny(AllRunningDataTasks);
// ***Remove the selected task from the list so that you don't
// process it more than once.
AllRunningDataTasks.Remove(firstFinishedTask);
// TODO: Await the completed task.
var taskOfTableResult = await firstFinishedTask;
// Todo: (doen't work)
TrustState thisState = (TrustState)firstFinishedTask.AsyncState;
// TODO: Update the concurrent dictionary with data
// thisState.QueryStartPoint + thisState.ThingToSearchFor
Interlocked.Decrement(ref thisState.RunningDirectQueries);
Interlocked.Increment(ref thisState.CompletedDirectQueries);
if (thisState.RunningDirectQueries == 0)
{
thisState.TimeCompleted = DateTime.UtcNow;
}
}
To answer the specific question "what logically executes an IQueryable to something that returns data"? That would be anything that either forces the production of at least one value, or forces the discovery of whether a value is available.
For example, ToList, ToArray, First, Single, SingleOrDefault, and Count will all force evaluation. (Although First will not evaluate the entire collection - it'll retrieve the first item and then stop.) These all have to at least start retrieving values, because there's no way for any of them to return what they return without doing so. In the case of ToList and ToArray, these return fully-populated non-lazy collections, which is why they have to evaluate everything. The methods that return a single item need to at least ask for the first item, and the Single ones will then go on to check that nothing else comes out if evaluation continues (and throw an exception if there turns out to be more).
Using foreach to iterate over the query will also force evaluation. (And again, it's for the same reason: you're asking it for actual values from the collection so it has to provide them.)
Concat does not evaluate immediately because it doesn't need to - it's only when you ask the concatenated sequence for a value that it needs to ask its inputs for values.
BTW, although you asked about IQueryable you're not using that in the examples here. This can matter, because there are some differences in how that works compared to the LINQ to Objects implementation (which you get for plain IEnumerable<T>) that you're actually getting. I don't think it makes a difference in this example, but it makes me wonder if something might have changed between your original code, and the version you posted for illustration here? It can matter because different LINQ providers can do things different ways. The IEnumerable<T> flavour of Concat definitely uses deferred evaluation, and although I'd expect that to be true for most other LINQ implementations, it's not absolutely a given.
If you need to use the results multiple times, and you want to ensure that you only evaluate them once, but that you don't evaluate them until you actually need them, then the usual approach is to call ToList at the point where you definitely need to evaluate, and then hold onto the resulting List<T> so you can use it again. Once you've got the data in List<T> (or array) form you can use that list as many times as you like.
By the way, your first question has an issue:
"How do I merge a List of TPL-based tasks for later execution?"
In general, if you already have a TPL task then you can't stop it from executing. (There is an exception to this. If you construct a Task directly instead of using one of the more normal ways of creating it, it won't actually run until you tell it to. But in general, APIs that return tasks return live ones, i.e., they may well already be running, or even complete, by the time you get your hands on them.)
The "later execution" in your example comes from the fact that you don't actually have a list of tasks at all to start with. (If you did in fact have a List<T> of tasks, "later execution" would not be an option.) What you have is a couple of enumerables which, if you were to evaluate them, would create tasks. The act of creating the task is indivisible from the act of starting it in any TAP-style API that returns a Task.
Based on the rest of what you wrote, I think what you are really asking is:
"How do I merge multiple IEnumerable<Task<T>> objects into a single IEnumerable<Task<T>> in a way that defers evaluation of the underlying enumerables until the combined enumerable itself is evaluated?"
Concat should work for that.
The following is a hacky way to get data merged... I don't like how I had to use "nothingList" in Main or a few other aspects of this sample, but it seems to get the job done and allow me to merge pending tasks.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
// Add a using directive and a reference for System.Net.Http.
using System.Net.Http;
// Add the following using directive.
using System.Threading;
namespace ProcessTasksAsTheyFinish
{
public partial class MainWindow : Window
{
// Declare a System.Threading.CancellationTokenSource.
CancellationTokenSource cts;
List<IEnumerable<Task>> launchList = new List<IEnumerable<Task>>();
public MainWindow()
{
InitializeComponent();
List<string> nothingList = new List<string>();
nothingList.Add("whatever");
cts = new CancellationTokenSource();
delayedExecution =
from str in nothingList
select AccessTheWebAsync("", cts.Token);
List<string> nothingList2 = new List<string>();
nothingList2.Add("whatever");
delayedExecution2 =
from str in nothingList2
select AccessTheWebAsync("1", cts.Token);
launchList.Add(delayedExecution);
launchList.Add(delayedExecution2);
delayedExecution = delayedExecution.Concat(delayedExecution2);
}
IEnumerable<Task> delayedExecution = null;
IEnumerable<Task> delayedExecution2 = null;
private async void startButton_Click(object sender, RoutedEventArgs e)
{
resultsTextBox.Clear();
// Instantiate the CancellationTokenSource.
try
{
// ***Set up the CancellationTokenSource to cancel after 25 seconds.
//cts.CancelAfter(250000);
var test = delayedExecution;// AccessTheWebAsync("", cts.Token);
var testList = test.ToList();
while (testList.Count() > 0)
{
var firstFinishedTask = await Task.WhenAny(testList);
testList.Remove(firstFinishedTask);
await firstFinishedTask;
}
resultsTextBox.Text += "\r\nDownloads complete.";
}
catch (OperationCanceledException tee)
{
resultsTextBox.Text += "\r\nDownloads canceled.\r\n";
}
catch (Exception)
{
resultsTextBox.Text += "\r\nDownloads failed.\r\n";
}
cts = null;
}
private void cancelButton_Click(object sender, RoutedEventArgs e)
{
if (cts != null)
{
cts.Cancel();
}
}
async Task<string> AccessTheWebAsync(string nothing, CancellationToken ct)
{
// CHANGE THIS VALUE TO CONTROL THE TESTING
bool delayConversionOfQueryToList = false;
HttpClient client = new HttpClient();
// Make a list of web addresses.
List<string> urlList = null;
if (nothing == "1")
{
urlList = SetUpURLList2();
}
else urlList = SetUpURLList();
// ***Create a query that, when executed, returns a collection of tasks.
IEnumerable<Task<int>> downloadTasksQuery =
from url in urlList select ProcessURL(url, client, ct);
// DEBUG!!!
if (delayConversionOfQueryToList == true)
{
await Task.Delay(10000);
resultsTextBox.Text += String.Format("\r\nDelay of IQueryable complete. Tip: Did you see any IsRunning messages?");
}
// ***Use ToList to execute the query and start the tasks.
List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
// DEBUG!!!
if (delayConversionOfQueryToList == false)
{
await Task.Delay(10000);
resultsTextBox.Text += String.Format("\r\nDelay of .ToList() complete. Tip: Did you see any IsRunning messages?");
}
// ***Add a loop to process the tasks one at a time until none remain.
while (downloadTasks.Count() > 0)
{
// Identify the first task that completes.
Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);
resultsTextBox.Text += String.Format("\r\nID {0}", firstFinishedTask.Id);
// ***Remove the selected task from the list so that you don't
// process it more than once.
downloadTasks.Remove(firstFinishedTask);
// Await the completed task.
int length = await firstFinishedTask;
resultsTextBox.Text += String.Format("\r\nLength of the download: {0}", length);
}
return nothing;
}
private List<string> SetUpURLList()
{
List<string> urls = new List<string>
{
"http://msdn.microsoft.com",
"http://msdn.microsoft.com/library/windows/apps/br211380.aspx",
"http://msdn.microsoft.com/en-us/library/hh290136.aspx",
"http://msdn.microsoft.com/en-us/library/dd470362.aspx",
"http://msdn.microsoft.com/en-us/library/aa578028.aspx",
"http://msdn.microsoft.com/en-us/library/ms404677.aspx",
"http://msdn.microsoft.com/en-us/library/ff730837.aspx"
};
return urls;
}
private List<string> SetUpURLList2()
{
List<string> urls = new List<string>
{
"http://www.google.com",
};
return urls;
}
async Task<int> ProcessURL(string url, HttpClient client, CancellationToken ct)
{
resultsTextBox.Text += String.Format("\r\nIS RUNNING {0}", url);
// GetAsync returns a Task<HttpResponseMessage>.
HttpResponseMessage response = await client.GetAsync(url, ct);
// Retrieve the website contents from the HttpResponseMessage.
byte[] urlContents = await response.Content.ReadAsByteArrayAsync();
// Thread.Sleep(3000);
// await Task.Delay(1000, ct);
return urlContents.Length;
}
}
}
// Sample Output:
IS RUNNING http://msdn.microsoft.com
IS RUNNING http://msdn.microsoft.com/library/windows/apps/br211380.aspx
IS RUNNING http://msdn.microsoft.com/en-us/library/hh290136.aspx
IS RUNNING http://msdn.microsoft.com/en-us/library/dd470362.aspx
IS RUNNING http://msdn.microsoft.com/en-us/library/aa578028.aspx
IS RUNNING http://msdn.microsoft.com/en-us/library/ms404677.aspx
IS RUNNING http://msdn.microsoft.com/en-us/library/ff730837.aspx
IS RUNNING http://www.google.com
Delay of .ToList() complete. Tip: Did you see any IsRunning messages?
ID 1
Length of the download: 48933
ID 2
Length of the download: 375328
ID 3
Length of the download: 220428
ID 4
Length of the download: 222256
ID 5
Length of the download: 229330
ID 6
Length of the download: 136544
ID 7
Length of the download: 207171
Delay of .ToList() complete. Tip: Did you see any IsRunning messages?
ID 8
Length of the download: 43945
Downloads complete.
Related
I'm trying to figure out how to refresh the client-side component after button click.
Repo Link with example: https://github.com/ovie91/RefreshComponent
Site /test or from nav menu test
So I have OnInitializedAsync method that is retrieving data from API
protected override async Task OnInitializedAsync()
{
result = await (some API Call);
}
Then I have a method connected to the button
private async void ButtonClick()
{
await (some API Call);
result = null;
this.StateHasChanged(); <--- Doesnt work :<
}
I have tried to use this.StateHasChanged(); but there is no reaction.
As a workaround, I can force you to navigate again to the same website but this refresh "Whole" website but not a component.
Any ideas on how to deal with it?
whole code (stripped to minimum):
#page "/test"
#inject HttpClient Http
#if (result == null)
{
<p>Loading...<p>
}
else
{
#result
<button #onclick="(() => ButtonClick())">Click</button>
}
#code {
private APIObject result;
protected override async Task OnInitializedAsync()
{
result = await (some API Call);
}
private async void ButtonClick()
{
await (some API Call);
result = null;
this.StateHasChanged(); <--- Doesnt work :<
}
}
Update
I want to refresh component so OnInitializedAsync would be triggered again and that would mean I don't have to run the same code again after button click. Hope you understand what I mean.
To get the desired output you just have to shuffle the lines a little, from:
private async void ButtonClick()
{
await (some API Call); // UI checks if an update is needed (No)
result = null; // now an update is needed
this.StateHasChanged(); <--- Doesnt work :< // actually: not needed
}
to:
private async Task ButtonClick()
{
result = null; // change the state
//this.StateHasChanged(); // not needed, a request is pending
await (some API Call); // should show '<h3>Loading</h3>' now
}
Note that the UI is updated when an await releases the Thread.
however, from your answer we get
var APICall = await Http.GetAsync("SomeAPI");
Thread.Sleep(2000);
This should work when Http.GetAsync("SomeAPI"); really is an async call and not just some stand-in pseudo code. Because Thread.Sleep(2000); will really freeze things.
If you want to make sure:
private async Task GetData()
{
await Task.Delay(1); // release the thread for rendering
var APICall = await Http.GetAsync("SomeAPI");
Random rnd = new Random();
Thread.Sleep(2000); // Task.Delay() is much preferred
result = "Random Number: " + rnd.Next();
}
Thread.Sleep() is appropriate to simulate some CPU (not I/O) intensive code. So I'm not saying it's wrong but be aware of the difference.
And it is much better to make eventhandlers async Task instead of async void but that is not the direct problem here.
From here:
Blazor uses a synchronization context (SynchronizationContext) to enforce a single logical thread of execution. A component's lifecycle methods and any event callbacks that are raised by Blazor are executed on the synchronization context.
Blazor Server's synchronization context attempts to emulate a single-threaded environment so that it closely matches the WebAssembly model in the browser, which is single threaded. At any given point in time, work is performed on exactly one thread, giving the impression of a single logical thread. No two operations execute concurrently.
So as enet asnwered, you should use async Task signature instead of async void.
I have moved API call to another Method and inside of OnInitializedAsync I called it.
Then when I reset the result variable to see Loading state I'm able to "refresh" component to achieve that you need to add. this.StateHasChanged()
Now I have a responsive component to updates that are happening :)
#page "/test"
#using System.Threading;
#inject HttpClient Http
#if (result == null)
{
<h3>Loading</h3>
}
else
{
#result
<button #onclick="(() => ButtonClick())">Click</button>
}
#code {
private string result;
protected override async Task OnInitializedAsync()
{
await GetData();
}
private async Task GetData()
{
var APICall = await Http.GetAsync("SomeAPI");
Random rnd = new Random();
Thread.Sleep(2000);
result = "Random Number: " + rnd.Next();
}
private async Task ButtonClick()
{
await Http.GetAsync("SomeAPIcall");
result = null; // required to see loading state.
this.StateHasChanged(); // when added model is refreshed and Loading state is visible.
await GetData();
}
}
I'm new to async / await, and have been trying to implement it in my 4.6 web api 2 project.
public class MyController : ApiController
{
public async Task<Thing> Search(String searchTerms)
{
myThing = new Thing();
myThing.FirstProperty = await doFirstPropertyAsync(searchTerms);
myThing.SecondProperty = await doSecondPropertyAsync(searchTerms);
return myThing;
}
}
Basically I'm returning a class (Thing) that has two properties that take a few seconds each to populate. I'm actually loading maybe ~10 properties, but it's the same logic for all of them.
public async Task<MyCoolSubObject> doFirstPropertyAsync(string searchTerms)
{
SomeController sController = new SomeController();
Debug.WriteLine("first - starting.");
var x = await Task.Run(()=>sController.Lookup(searchTerms));
Debug.WriteLine("first - finishing.");
return x;
}
public async Task<MyCoolSubObject> doSecondPropertyAsync(string searchTerms)
{
SomeOtherController sController = new SomeOtherController();
Debug.WriteLine("second - starting.");
var x = await Task.Run(()=>sController.Lookup(searchTerms));
Debug.WriteLine("second - finishing.");
return x;
}
What's got my scratching my head:
When I look at the debug outputs, the first property assignment method call starts and finishes before the second completes. Again, I actually have like ten of these and no matter what order I put the property assignments in they complete in a serial fashion (ie: nothing starts until another one finishes).
These property assignments under the hood are basically doing database calls that take a while, hence I wanted them running in parallel if possible. The methods themselves ( SomeController.Lookup(string) ) contain no await/async/task stuff.
Again, I actually have like ten of these and no matter what order I
put the property assignments in they complete in a serial fashion (ie:
nothing starts until another one finishes).
This happens because in your code you use the await keyword as soon as you kickoff the task, by doing that you prevent the method to continue to execute the next statement before the task will be done.
If you want to run your tasks in parallel you should kickoff all of them and only then await all of them using Task.WhenAll:
public async Task<Thing> Search(String searchTerms)
{
myThing = new Thing();
var firstTask = doFirstPropertyAsync(searchTerms);
var secondTask = doSecondPropertyAsync(searchTerms);
await Task.WhenAll(firstTask, secondTask);
myThing.FirstProperty = await firstTask;
myThing.SecondProperty = await secondTask;
return myThing;
}
Note that when we await every task separately after we await Task.WhenAll the tasks have already been done, we do that in order to get the result from the task, although we can use the Result property (it will not block since we know the task has already been done) I prefer to use await for consistency reasons.
We have a Windows Phone 7 application which uses a set of 3 service methods using Reactive Extensions, defined as follows:
public static class ServiceClient
{
public static IObservable<string> LookupImage(byte[] image) {...}
public static IObservable<XDocument> GetDefinition(string id) {...}
public static IObservable<Dictionary<string, byte[]>> GetFiles(string id, string[] fileNames) {...}
}
We need the WP7 application to keep calling LookupImage in the above client (each time with different set of byte[] image data) until the returned IObservable<string> is nonempty. After we get the Observable string we have to call GetDefinition and GetFiles methods (in that order).
The calls to LookupImage should happen as often as the service response is returned as opposed to being controlled by a timer as it will vary depending on network connection speed and we need to be able to send as many of these as possible.
I'd appreciate any pointers to what might be a solution to the above. As a start I have the following
private void RunLookupAndRenderLogic()
{
byte[] imageBytes = GetImageBytes();
// There are some cases where the image was not 'interesting' enough in which case GetImageBytes() returns null
if (pictureBytes != null)
{
// Where we have image data, send this to LookupImage service method
var markerLookup = ServiceClient.LookupImage(imageBytes);
markerLookup.Subscribe(id =>
{
// If the id is empty, we need to call this again.
if (String.IsNullOrEmpty(id))
{
???
}
// If we have an id, call GetDefinition and GetFiles methods of the service. No further calls to LookupImage should take place.
RenderLogic(id);
});
}
else
// If no interesting image was returned, try again
RunRecognitionAndRenderLogic();
}
Apologies if I get this wrong, but if I understand it correctly you want to Retry the call to LookupImage with the exact same argument, until it returns a value?
A naive way of solving this would be to simply call repeat and then take(1):
ServiceClient.LookupImage(imageBytes)
.Repeat()
.Take(1)
.Subscribe(id => ....);
However as Rx is single threaded by default, there is no point in this context that allows us to inject our disposal call (implicit from the Take(1)-->OnComplete()-->Auto disposal of subscription).
You can dodge this by offering some breathing space between subsequent re-subscriptions by using the CurrentThread Scheduler.
Observable.Defer(()=>
ServiceClient.LookupImage(imageBytes)
.ObserveOn(Scheduler.CurrentThread)
)
.Repeat()
.Take(1)
.Subscribe(id => ....);
There are other ways of achieving this with some good understanding of Rx and some creativity. (Most I would imagine a Scheduler)
To give you some inspriation check out the chapter on Scheduling and Threading. It covers recursion and building your own iterator which is effectively what you are trying to do.
Full code sample:
private void RunLookupAndRenderLogic()
{
byte[] imageBytes = GetImageBytes();
// There are some cases where the image was not 'interesting' enough in which case GetImageBytes() returns null
if (pictureBytes != null)
{
// Where we have image data, send this to LookupImage service method
var subscription = Observable
.Defer(()=>
ServiceClient.LookupImage(imageBytes)
.ObserveOn(Scheduler.CurrentThread)
)
.Where(id=>!String.IsNullOrEmpty(id))
.Repeat()
.Take(1)
.Subscribe(id =>
{
// If we have an id, call GetDefinition and GetFiles methods of the service. No further calls to LookupImage should take place.
RenderLogic(id);
});
//TODO: You dont offer any way to cancel this (dispose of the suscription).
//This means you could loop forever :-(
}
else
{
// If no interesting image was returned, try again
RunRecognitionAndRenderLogic();
}
}
(Disclosure: I am the author of IntroToRx.com)
I have a list of addresses that i want to visit using httpWebRequest.
All i need is the statuscode returned by the server.
I have tried to foreach through them and begin a httpWebRequest on each of them, but then i only receive the callback from the last one.
It seems like only one webrequest is allowed at a time.
I'm having quite a hard time understanding how to do this without the GetResponse, which is not allowed in silverlight.
The code is running in a backgroundworker.
And i am using Mango - WP7.1
How do i solve that?
foreach (var current in Addresses)
{
var request = HttpWebRequest.Create(current);
request.BeginGetResponse(r =>
{
try
{
var response = (HttpWebResponse)request.EndGetResponse(r);
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
//BOOM RECEIVED
});
}
catch (Exception)
{
Debug.WriteLine("Error in EndGetResponse");
}
}, null);
}
Thanks in advance =)
Your problem of a single response is most likely being caused by your use of anonymous methods and the the way scoping works when you put these inside loops. You are throwing away the earlier request references on each step through the loop.
See my blogpost on the topic here http://csainty.blogspot.com/2010/10/windows-phone-7asynchronous-programming.html
The simplest way to illustrate this is to rewrite your code with full methods, this forces you to consider the scope instead of just blindly referening external variables in your delegates.
foreach (var current in Addresses)
{
var request = HttpWebRequest.Create(current);
request.BeginGetResponse(EndGetResponse, new RequestState { Request = request, Address = current });
}
private void EndGetResponse(IAsyncResult result) {
try {
var state = (RequestState)result.AsyncState;
var response = (HttpWebResponse)state.Request.EndGetResponse(result);
Deployment.Current.Dispatcher.BeginInvoke(GotResponse, state.Address, response.StatusCode);
} catch (Exception) {
Debug.WriteLine("Error in EndGetResponse");
}
}
private void GotResponse(Address address, HttpStatusCode code) {
//BOOM RECEIVED
}
public class RequestState {
HttpWebRequest Request { get; set; }
Address Address { get; set; }
}
Once you solve the scoping issues you can rewrite back into anonymos methods for stylistic reasons if you like.
This will only solve your first problem of getting all the responses back however, I assume you also need to run some code when all the requests are complete to check the status of the whole batch?
That is a different problem altogether.
You can not use WaitOne() or anything like that, it will lock your thread and stop the requests from actually running at all. You will probably want to call off to another method in you BOOM code that stores away the result and checks if all the results are in yet.
Boot Pros,
I recently started to program in spring-boot and I stumbled upon a question where I would like to get your opinion on.
What I try to achieve:
I created a Controller that exposes a GET endpoint, named nonBlockingEndpoint. This nonBlockingEndpoint executes a pretty long operation that is resource heavy and can run between 20 and 40 seconds.(in the attached code, it is mocked by a Thread.sleep())
Whenever the nonBlockingEndpoint is called, the spring application should register that call and immediatelly return an Operation ID to the caller.
The caller can then use this ID to query on another endpoint queryOpStatus the status of this operation. At the beginning it will be started, and once the controller is done serving the reuqest it will be to a code such as SERVICE_OK. The caller then knows that his request was successfully completed on the server.
The solution that I found:
I have the following controller (note that it is explicitely not tagged with #Async)
It uses an APIOperationsManager to register that a new operation was started
I use the CompletableFuture java construct to supply the long running code as a new asynch process by using CompletableFuture.supplyAsync(() -> {}
I immdiatelly return a response to the caller, telling that the operation is in progress
Once the Async Task has finished, i use cf.thenRun() to update the Operation status via the API Operations Manager
Here is the code:
#GetMapping(path="/nonBlockingEndpoint")
public #ResponseBody ResponseOperation nonBlocking() {
// Register a new operation
APIOperationsManager apiOpsManager = APIOperationsManager.getInstance();
final int operationID = apiOpsManager.registerNewOperation(Constants.OpStatus.PROCESSING);
ResponseOperation response = new ResponseOperation();
response.setMessage("Triggered non-blocking call, use the operation id to check status");
response.setOperationID(operationID);
response.setOpRes(Constants.OpStatus.PROCESSING);
CompletableFuture<Boolean> cf = CompletableFuture.supplyAsync(() -> {
try {
// Here we will
Thread.sleep(10000L);
} catch (InterruptedException e) {}
// whatever the return value was
return true;
});
cf.thenRun(() ->{
// We are done with the super long process, so update our Operations Manager
APIOperationsManager a = APIOperationsManager.getInstance();
boolean asyncSuccess = false;
try {asyncSuccess = cf.get();}
catch (Exception e) {}
if(true == asyncSuccess) {
a.updateOperationStatus(operationID, Constants.OpStatus.OK);
a.updateOperationMessage(operationID, "success: The long running process has finished and this is your result: SOME RESULT" );
}
else {
a.updateOperationStatus(operationID, Constants.OpStatus.INTERNAL_ERROR);
a.updateOperationMessage(operationID, "error: The long running process has failed.");
}
});
return response;
}
Here is also the APIOperationsManager.java for completness:
public class APIOperationsManager {
private static APIOperationsManager instance = null;
private Vector<Operation> operations;
private int currentOperationId;
private static final Logger log = LoggerFactory.getLogger(Application.class);
protected APIOperationsManager() {}
public static APIOperationsManager getInstance() {
if(instance == null) {
synchronized(APIOperationsManager.class) {
if(instance == null) {
instance = new APIOperationsManager();
instance.operations = new Vector<Operation>();
instance.currentOperationId = 1;
}
}
}
return instance;
}
public synchronized int registerNewOperation(OpStatus status) {
cleanOperationsList();
currentOperationId = currentOperationId + 1;
Operation newOperation = new Operation(currentOperationId, status);
operations.add(newOperation);
log.info("Registered new Operation to watch: " + newOperation.toString());
return newOperation.getId();
}
public synchronized Operation getOperation(int id) {
for(Iterator<Operation> iterator = operations.iterator(); iterator.hasNext();) {
Operation op = iterator.next();
if(op.getId() == id) {
return op;
}
}
Operation notFound = new Operation(-1, OpStatus.INTERNAL_ERROR);
notFound.setCrated(null);
return notFound;
}
public synchronized void updateOperationStatus (int id, OpStatus newStatus) {
iteration : for(Iterator<Operation> iterator = operations.iterator(); iterator.hasNext();) {
Operation op = iterator.next();
if(op.getId() == id) {
op.setStatus(newStatus);
log.info("Updated Operation status: " + op.toString());
break iteration;
}
}
}
public synchronized void updateOperationMessage (int id, String message) {
iteration : for(Iterator<Operation> iterator = operations.iterator(); iterator.hasNext();) {
Operation op = iterator.next();
if(op.getId() == id) {
op.setMessage(message);
log.info("Updated Operation status: " + op.toString());
break iteration;
}
}
}
private synchronized void cleanOperationsList() {
Date now = new Date();
for(Iterator<Operation> iterator = operations.iterator(); iterator.hasNext();) {
Operation op = iterator.next();
if((now.getTime() - op.getCrated().getTime()) >= Constants.MIN_HOLD_DURATION_OPERATIONS ) {
log.info("Removed operation from watchlist: " + op.toString());
iterator.remove();
}
}
}
}
The questions that I have
Is that concept a valid one that also scales? What could be improved?
Will i run into concurrency issues / race conditions?
Is there a better way to achieve the same in boot spring, but I just didn't find that yet? (maybe with the #Async directive?)
I would be very happy to get your feedback.
Thank you so much,
Peter P
It is a valid pattern to submit a long running task with one request, returning an id that allows the client to ask for the result later.
But there are some things I would suggest to reconsider :
do not use an Integer as id, as it allows an attacker to guess ids and to get the results for those ids. Instead use a random UUID.
if you need to restart your application, all ids and their results will be lost. You should persist them to a database.
Your solution will not work in a cluster with many instances of your application, as each instance would only know its 'own' ids and results. This could also be solved by persisting them to a database or Reddis store.
The way you are using CompletableFuture gives you no control over the number of threads used for the asynchronous operation. It is possible to do this with standard Java, but I would suggest to use Spring to configure the thread pool
Annotating the controller method with #Async is not an option, this does not work no way. Instead put all asynchronous operations into a simple service and annotate this with #Async. This has some advantages :
You can use this service also synchronously, which makes testing a lot easier
You can configure the thread pool with Spring
The /nonBlockingEndpoint should not return the id, but a complete link to the queryOpStatus, including id. The client than can directly use this link without any additional information.
Additionally there are some low level implementation issues which you may also want to change :
Do not use Vector, it synchronizes on every operation. Use a List instead. Iterating over a List is also much easier, you can use for-loops or streams.
If you need to lookup a value, do not iterate over a Vector or List, use a Map instead.
APIOperationsManager is a singleton. That makes no sense in a Spring application. Make it a normal PoJo and create a bean of it, get it autowired into the controller. Spring beans by default are singletons.
You should avoid to do complicated operations in a controller method. Instead move anything into a service (which may be annotated with #Async). This makes testing easier, as you can test this service without a web context
Hope this helps.
Do I need to make database access transactional ?
As long as you write/update only one row, there is no need to make this transactional as this is indeed 'atomic'.
If you write/update many rows at once you should make it transactional to guarantee, that either all rows are updated or none.
However, if two operations (may be from two clients) update the same row, always the last one will win.