I have this line, which does a blocking (synchronous) web service call, and works fine:
var response = client.Execute<DataRequestResponse>(request);
(The var represents IRestResponse<DataRequestResponse>)
But, now I want to be able to cancel it (from another thread). (I found this similar question, but my code must stay sync - the code changes have to stay localized in this function.)
I found CancellationTokenSource, and an ExecuteTaskAsync() which takes a CancellationToken. (See https://stackoverflow.com/a/21779724/841830) That sounds like it could do the job. I got as far as this code:
var cancellationTokenSource = new CancellationTokenSource();
var task = client.ExecuteTaskAsync(request, cancellationTokenSource.Token);
task.Wait();
var response = task.Result;
The last line refuses to compile, telling me it cannot do an implicit cast. So I tried an explicit cast:
IRestResponse<DataRequestResponse> response = task.Result as IRestResponse<DataRequestResponse>;
That compiles, runs, but then crashes (throws a NullReferenceException, saying "Object reference not set to an instance of an object").
(By the way, once I have this working, then the cancellationTokenSource.Token will of course be passed in from the master thread, and I will also be adding some code to detect when an abort happened: I will throw an exception.)
My back-up plan is just to abort the whole thread this is running in. Crude, but that is actually good enough at the moment. But I'd rather be doing it "properly" if I can.
MORE INFORMATION:
The sync Execute call is here: https://github.com/restsharp/RestSharp/blob/master/RestSharp/RestClient.Sync.cs#L55 where it ends up calling Http.AsGet() or Http.AsPost(), which then ends up here: https://github.com/restsharp/RestSharp/blob/master/RestSharp/Http.Sync.cs#L194
In other words, under the covers RestSharp is using HttpWebRequest.GetResponse. That has an Abort function, but being a sync function (i.e. which does not return until the request is done) that is not much use to me!
The async counterpart of your call
var response = client.Execute<DataRequestResponse>(request);
is the public virtual Task<IRestResponse<T>> ExecuteTaskAsync<T>(IRestRequest request, CancellationToken token) RestSharp async method.
It takes a cancellation token, and returns a task with the correct return type signature. To use it and wait for its (cancellable) completion, you could change your sample code to:
var cancellationTokenSource = new CancellationTokenSource();
// ...
var task = client.ExecuteTaskAsync<DataRequestResponse>(
request,
cancellationTokenSource.Token);
// this will wait for completion and throw on cancellation.
var response = task.Result;
Given this sync call:
var response = client.Execute<MyData>(request);
process(response);
Change it to:
var response = null;
EventWaitHandle handle = new AutoResetEvent (false);
client.ExecuteAsync<MyData>(request, r => {
response = r;
handle.Set();
});
handle.WaitOne();
process(response);
That is equivalent to the sync version, no benefit is gained. Here is one way to then add in the abort functionality:
bool volatile cancelAllRequests = false;
...
var response = null;
EventWaitHandle handle = new AutoResetEvent (false);
RestRequestAsyncHandle asyncRequest = client.ExecuteAsync<MyData>(request, r => {
response = r;
handle.Set();
});
while(true){
if(handle.WaitOne(250))break; //Returns true if async operation finished
if(cancelAllRequests){
asyncRequest.WebRequest.Abort();
return;
}
}
process(response);
(Sorry, couldn't wait for the bounty to bear fruit, so had to work this out for myself, by trial and error this afternoon...)
Related
In my .Net 6 WebPI service, I am queueing work to a background task queue, very closely based on the example here, but I could post parts of my implementation if that would help:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-6.0&tabs=visual-studio#queued-background-tasks
I am running into unexpected behavior where control is not returned to the caller, even after the return Ok(..) completes in the controller. Instead the request only completes after the await Task.Delay(1000); line is reached on the queued work item. The request returns to the client as soon as this line is reached, and does not need to wait for the Delay to finish.
I'm guessing this is because of the await either starting a new async context, or someone un-sticking the async context of the original request. My intention is for the request to complete immediately after queuing the work item.
Any insight into what is happening here would be greatly appreciated.
Controller:
public async Task<ActionResult> Test()
{
var resultMessage = await _testQueue.DoIt();
return Ok(new { Message = resultMessage });
}
Queueing Work:
public TestAdapter(ITaskQueue taskQueue)
{
_taskQueue = taskQueue;
}
public async Task<string> DoIt()
{
await _taskQueue.QueueBackgroundWorkItemAsync(async (_cancellationToken) =>
{
await Task.Delay(1000);
var y = 12;
});
return "cool";
}
IoC:
services.AddHostedService<QueueHostedService>();
services.AddSingleton<ITTaskQueue>(ctx =>
{
return new TaskQueue(MaxQueueCount);
});
TaskQueue:
private readonly Channel<BackgroundTaskContext> _queue;
public TaskQueue(int capacity)
{
var options = new BoundedChannelOptions(capacity)
{
FullMode = BoundedChannelFullMode.Wait
};
_queue = Channel.CreateBounded<BackgroundTaskContext>(options);
}
public async ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
await _queue.Writer.WriteAsync(new BackgroundTaskContext(workItem, ...));
}
Not sure what you expect here. I'm assuming you want the async method to return the cool in the api response. That's fine but because your also awaiting the async call with in DoIt(), then it pauses until QueueBackgroundWorkItemAsync finishes. You could remove the await and it will run and return as you expect.
I can't say that I'm a big fan of that design as you lose contact with it with exception of the cancellation token. Another thought would be to Send that work off to a console job or function app using message bus or even another http call.
Additional Notes:
Async can be complicated to explain because in reality, it wraps up code and executes on a thread of it's choosing. The await simulates the synchronous behavior.
await Task.Delay(1000); // Runs on it's own thread but still halts code execution for 1 second.
await _taskQueue.QueueBackgroundWorkItemAsync(async (_cancellationToken) // Waits for control to be returned from the code inside.
var resultMessage = await _testQueue.DoIt(); // Always waits for the code inside to complete.
If your wanting something to run without pausing code execution, you can either remove the await or add a Task.Run(() => { }); pattern. Is it a good idea is a whole other question. It also matters whether you need information back from the async method. If you don't await it then you'll get null back as it doesn't wait around for the answer to be computed.
This appears just to have been user error using the debugger. The debugger is switching to the background task thread and hitting breakpoints there before the response fully returns giving the appearance that control was not being returned to the client and being carried into the background task.
Even after adding some synchronous steps in QueueBackgroundWorkItemAsync and putting breakpoints on them, control was not immediately returned to the original http call. Only after I tried adding a long running task like await Task.Delay(1000); did enough time/ticks pass for the http response to return. I had conflated this with just the await somehow freeing up the original http context.
I'm in a bit of a conundrum regarding multithreading.
I'm currently working on a real-time service using SinglaR. The idea is that a connected user can request data from another.
Below is a gist of what the request and response functions look like.
Consider the following code:
private readonly ConcurrentBag _sharedObejcts= new ConcurrentBag();
The request:
[...]
var sharedObject = new MyObject();
_sharedObejcts.Add(sharedObject);
ForwardRequestFireAndForget();
try
{
await Task.Delay(30000, sharedObject.myCancellationToken);
}
catch
{
return sharedObject.ResponseProperty;
}
_myConcurrentBag.TryTake(sharedObject);
[...]
The response:
[...]
var result = DoSomePossiblyVeryLengthyTaskHere();
var sharedObject = ConcurrentBag
.Where(x)
.FirstOrDefault();
// The request has timed out so the object isn't there anymore.
if(sharedObject == null)
{
return someResponse;
}
sharedObject.ResponseProperty = result;
// triggers the cancellation source
sharedObject.Cancel();
return someOtherResponse;
[...]
So basically a request is made to the server, forwarded to the other host and the function waits for cancellation or it times out.
The other hosts call the respond function, which adds the repsonseObject and triggers myCancellationToken.
I am however unsure whether this represents a race condition.
In theory, could the responding thread retrieve the sharedObject while the other thread still sits on the finally block?
This would mean, the request timed out already, the task just hasn't gotten around to removing the object from the bag, which means the data is inconsistent.
What would be some guaranteed ways to make sure that the first thing that gets called after the Task.Delay() call is the TryTake()call?
You don't want to have the producer cancel the consumer's wait. That's way too much conflation of responsibilities.
Instead, what you really want is for the producer to send an asynchronous signal. This is done via TaskCompletionSource<T>. The consumer can add the object with an incomplete TCS, and then the consumer can (asynchronously) wait for that TCS to complete (or timeout). Then the producer just gives its value to the TCS.
Something like this:
class MyObject
{
public TaskCompletionSource<MyProperty> ResponseProperty { get; } = new TaskCompletionSource<MyProperty>();
}
// request (consumer):
var sharedObject = new MyObject();
_sharedObejcts.Add(sharedObject);
ForwardRequestFireAndForget();
var responseTask = sharedObject.ResponseProperty.Task;
if (await Task.WhenAny(Task.Delay(30000), responseTask) != responseTask)
return null;
_myConcurrentBag.TryTake(sharedObject);
return await responseTask;
// response (producer):
var result = DoSomePossiblyVeryLengthyTaskHere();
var sharedObject = ConcurrentBag
.Where(x)
.FirstOrDefault();
// The request has timed out so the object isn't there anymore.
if(sharedObject == null)
return someResponse;
sharedObject.ResponseProperty.TrySetResult(result);
return someOtherResponse;
The code above can be cleaned up a bit; specifically, it's not a bad idea to have the producer have a "producer view" of the shared object, and the consumer have a "consumer view", with both interfaces implemented by the same type. But the code above should give you the general idea.
Besides the most common form of calling TaskFactory.StartNew with only the "action" parameter
(1) https://msdn.microsoft.com/en-us/library/dd321439(v=vs.110).aspx
we alse have one method that accepts an extra parameter as the "Cancelation Token"
(2) https://msdn.microsoft.com/en-us/library/dd988458.aspx
My question is, why should we use the call (2) instead of call (1)?
I mean, the example in MSDN for page (2) would also work if I don't pass the Cancellation Token as parameter (because the variable token is accessible from the delegate function. Something like:
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var files = new List<Tuple<string, string, long, DateTime>>();
var t = Task.Factory.StartNew( () => { string dir = "C:\\Windows\\System32\\";
object obj = new Object();
if (Directory.Exists(dir)) {
Parallel.ForEach(Directory.GetFiles(dir),
f => {
if (token.IsCancellationRequested)
token.ThrowIfCancellationRequested();
var fi = new FileInfo(f);
lock(obj) {
files.Add(Tuple.Create(fi.Name, fi.DirectoryName, fi.Length, fi.LastWriteTimeUtc));
}
});
}
}
); //note that I removed the ", token" from here
tokenSource.Cancel();
So is there anything happening underneath when I pass cancellation token to Task.Factory.StartNew?
Thanks
Two things will happen.
If the token has been canceled before StartNew is called it will never start the thread and the task will be in the Canceled state.
If a OperationCanceledException is raised from inside the task and that exception was passed in the same token as StartNew it will cause the returned Task to enter the Cancelled state. If the token associated with the exception is a different token or you did not pass a token in the task will enter the Faulted state.
P.S. You should never call Task.Factory.StartNew without passing in a TaskScheduler because if you don't it can easily cause you to run code on the UI thread that you expected to run on a background thread. Use Task.Run( instead unless you absoultely need to use StartNew, Task.Run has the same CancellationToken behavior as StartNew.
Situation
I have a raspberry pi set up as a server taking json via HTTP as input.
The "API" allows to set leds connected to the pi. That all works, I can send requests from the browser and everything is great.
It takes a while for the response to arrive. That's why I want to communicate asynchrounously.
I found this on msdn that explains how it's done.
// Three things to note in the signature:
// - The method has an async modifier.
// - The return type is Task or Task<T>. (See "Return Types" section.)
// Here, it is Task<int> because the return statement returns an integer.
// - The method name ends in "Async."
async Task<int> AccessTheWebAsync()
{
// You need to add a reference to System.Net.Http to declare client.
HttpClient client = new HttpClient();
// GetStringAsync returns a Task<string>. That means that when you await the
// task you'll get a string (urlContents).
Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
// You can do work here that doesn't rely on the string from GetStringAsync.
DoIndependentWork();
// The await operator suspends AccessTheWebAsync.
// - AccessTheWebAsync can't continue until getStringTask is complete.
// - Meanwhile, control returns to the caller of AccessTheWebAsync.
// - Control resumes here when getStringTask is complete.
// - The await operator then retrieves the string result from getStringTask.
string urlContents = await getStringTask;
// The return statement specifies an integer result.
// Any methods that are awaiting AccessTheWebAsync retrieve the length value.
return urlContents.Length;
}
For the top level overview, here's how my Main method looks like (it doesn't compile):
var pi1 = new RaspberryPi(#"http://192.168.0.100:8080"); // specify IP
var led = new Led(255, 100, 180); // r, g, b values wrapped in an Led object
Led result = await pi1.setLedAsync(2, led); // FAIL // what should be an async POST, awaiting the response
I hope that makes sense.
The Led class is just a data object holding 3 bytes for the 3 channels and some conversion methods to and from json.
The setLedAsync method:
async public Task<Led> setLedAsync(uint n, Led led)
{
var client = new HttpClient();
client.BaseAddress = _uri;
var content = new StringContent(led.ToJson(), Encoding.UTF8, "application/json");
Task<HttpResponseMessage> response = client.PutAsync("/led/" + n, content);
HttpResponseMessage responseMessage = await response;
string json = await responseMessage.Content.ReadAsStringAsync();
return new Led(json);
}
Error
This line is where I get an error for using await:
Led result = await pi1.setLedAsync(2, led);
await can only be used in an async method.
Questions
Why do I get this error? The last comment line in the example code
// Any methods that are awaiting AccessTheWebAsync retrieve the length value.
makes me think that this is how it should be done. As I understand it, the await basically unwrapps the Task<T> into a T.
If I do not use await, I get a type missmatch, because the method returns Task<Led>, not Led.
What's confusing for me is to understand the difference between the example and my situation. I have to use await twice in my async method:
HttpResponseMessage responseMessage = await response;
string json = await responseMessage.Content.ReadAsStringAsync();
The thing is that I have to deal with this HttpResponseMessage as a middleman. I suspect that I'm prematurely giving up the asynchronousity with this second await somehow (if that makes any sense) I think this is the origin of the problem, but I'm not sure how to solve it.
Edit
I wrapped the function call in an asyn method, which allows to compile the code.
But it's not asynchronous. I added a delay on the server side to test this.
class Program
{
static void Main(string[] args)
{
var prog = new Program();
Console.WriteLine("before init");
prog.init();
Console.WriteLine("after init"); // not happening before the response arrives
Console.Read();
}
private async void init()
{
var pi1 = new RaspberryPi(#"http://192.168.0.100:8080"); // specify IP
var led = new Led(255, 0, 0); // r, g, b values wrapped in an Led object
Console.WriteLine("before await");
Led result = await pi1.setLedAsync(2, led); // what should be an async POST, awaiting the response
Console.WriteLine("after await");
}
}
None of the "after" messages are written to the console before the response from the request arrives.
You get the error because an asynchronous wait -- an await -- implies that the method doing the awaiting is itself asynchronous. I think you do not understand what await means. Await does not mean synchronously block until the result is available -- that is the opposite of what it means. Await means return immediately so my caller can do important work without waiting for this result; schedule the remainder of this method at some time in the future when the result is available. Await is an asynchronous wait. When you await a letter to arrive in the mail you do not sit by the door doing nothing until it arrives; you do other work asynchronously and then resume the task of reading your mail at some time after the letter arrives.
You say that the method -- the method doing the awaiting, not the method returning the task being awaited -- is Main. If this is a console app then you cannot make Main asynchronous because when Main returns the program ends. Again, internalize this: an await is just a return from the perspective of the current method. The current method will be called again later in the future and will pick up where it left off, but when Main returns there is no future. So you cannot make Main async.
You note that async turns a task of T into a T, which is correct, but it does so asynchronously. So your principal choices here are here are:
turn the task of T into a T synchronously from Main
write some sort of app other than a Console app; say, a winforms or WPF app, which does not terminate until you tell it to
I want to use the BeginGetResponse method in order to call many URLs which I hold on a list.
I have 2 questions on how to implement this:
according to the example in http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.begingetresponse(v=vs.95).aspx
we use:
public static ManualResetEvent allDone= new ManualResetEvent(false);
is that wise to use a static member in web application since it's shared with other threads ? can this cause for problems ?
how can I tell when all the callbacks were finished ? I need to do a summary report on the results
Thanks
While you can use an event, I'd recommend using Task<T> and the FromAsync method on the TaskFactory class like so:
// Execution of tasks starts here because of the
// call to ToArray.
Task<WebResponse>[] tasks = uris.Select(u => {
// Create the request.
WebRequest req = ...;
// Make the call to return the response asynchronously with
// a Task.
return Task.Factory.FromAsync(req.BeginGetResponse,
req.EndGetResponse, null);
}).ToArray();
Once you have that, you can easily wait on all of the Task<T> instances using the ContinueWhenAll method on the TaskFactory class like so with a continuation:
Task.Factory.ContinueWhenAll(tasks, t => {
// Note that t is an array of Task, so you have to cast
// each element to a Task<WebRequest>.
// Process all of them here.
});
Note the above returns a Task which you will have to wait on or continued on when done (if your're concerned about the notification).
If you are using .NET 4.5, you don't need to use the ContinueWhenAll method on the TaskFactory class, but can use the WhenAll method on the Task class to perform the work:
// Note that work does not start here yet because of deferred execution.
// If you want it to start here, you can call ToArray like above.
IEnumerable<Task<WebResponse>> tasks = uris.Select(u => {
// Create the request.
WebRequest req = ...;
// Make the call to return the response asynchronously with
// a Task.
return Task.Factory.FromAsync(req.BeginGetResponse,
req.EndGetResponse, null);
});
// Execution will start at this call:
Task<Task<WebRequest>[]> allTasks = Task.WhenAll(tasks);
// Continue or wait here.
Note that the above was before it was revealed that .NET 3.5 was being used.
If you want to wait in main thread for completition, then this solution is not very good. First request will change event's state to "set". Therefore, main thread will continue execution after the first request completed.
A suggest you to use CountdownEvent:
using(var countdownEvent = new CountdownEvent(list.Count))
{
// launch requests with countdownEvent.Signal(); in the end
countdownEvent.Wait();
}
You must store reference to countdownEvent inside RequestState. Also, don't forget to control timeouts - start new thread with ThreadPool.RegisterWaitForSingleObject.
I guess you're trying to do something like this:
int total = urls.Count;
ManualResetEvent evt = new ManualResetEvent();
ConcurrentBag<WebResponses> responses = new ConcurrentBag<WebResponse>();
foreach(Uri in uri)
{
HttpWebRequest req = ...;
req.BeginGetResponse(res=>
{
WebResponse res = req.EndGetResponse();
// do what you need with the response.
// maybe add it to a collection so you can report on it later:
responses.Add(res);
if(Interlocked.Decrement(ref total) == 0)
{
// this was the last response. set event.
evt.Set();
}
}, null);
}
evt.Wait();
foreach(WebResponse res in responses)
{
// report something about the response.
}
Note that an optimal workflow will not need an event. For extra credit, get rid of it all together and move your final logic inside the if that sets the event.
Also, this code is untested and lacks error handling, so be sure to add that in if you play with it at all.