6

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!

Community
  • 1
  • 1
Darren Cook
  • 27,837
  • 13
  • 117
  • 217

2 Answers2

10

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; 
Alex
  • 13,024
  • 33
  • 62
  • Thanks for the answer. Does the above code need to be inside a function flagged with `async`? That was one of the problems I kept hitting when I tried to use the `ExecuteTaskAsync` functions. – Darren Cook Jun 02 '15 at 14:33
  • @DarrenCook while it is generally a good idea to bubble `async` all the way up your call stack, you don't have to. In the sample above you will notice there is no `await` statement, but instead a blocking wait using `response = task.Result;`. So the method containing this code should not be marked `async`. Alternatively you could make it `async Task MyMethod(...) { ... return await task; }` and have the client code that calls the `MyMethod` do the waiting (either blocking or using `await`). – Alex Jun 03 '15 at 02:24
0

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...)

Darren Cook
  • 27,837
  • 13
  • 117
  • 217