0

I've had a look around for some documentation, and all the documentation appears to suggest the following...

  1. PLINQ tasks do not have a default timeout
  2. PLINQ tasks can deadlock, and .NET/TPL will never cancel them for you to release the deadlock

However, in my application, this is not the case. I cannot replicate a minimal example using a simpler console app, but I will show the closest reproduction I have tried, and the actual PLINQ query that is being cancelled prior to completion. There is no Exception of any type other than the Task Cancellation Exceptions, which all suggest that the task was requested to be cancelled directly (no other Exception occurred anywhere). There is no cancellation code anywhere in my application, so it can only be .NET deciding to cancel it for me?

I am aware that these examples below hammer out HttpClients, this is not the cause, as the console example shows.

Attempt at reproduction, this code never cancels, despite the epic running time:

var j = 0;
var ints = new List<int>();

for (int i = 0; i < 5000; i++) {
    ints.Add(i);
};

ints.AsParallel().WithExecutionMode(ParallelExecutionMode.ForceParallelism).WithDegreeOfParallelism(8).ForAll(n => {
    int count = 0;

    while (count < 100 && j == 0) {
        var httpClient = new HttpClient();
        var response = httpClient.GetStringAsync("https://hostname/").GetAwaiter().GetResult();
        count++;
        Thread.Sleep(1000);
    }
});

But this code, usually gets a couple of minutes in before stalling. I'm not sure whether it stalls first, and then .NET notices this and cancels it (but that violates point 2...) or whether .NET just cancels it because it took overall too long (but that's point 1...). usernames contains 5000 elements in the running code, hence the console test with 5000 elements. Papi just wraps HttpClient and uses SendAsync to send HttpRequestMessage off, I can't see SendAsync being the cause though.

importSuccess = usernames.AsParallel().WithExecutionMode(ParallelExecutionMode.ForceParallelism).WithDegreeOfParallelism(8).All(u => {
    var apiClone = new Papi(api.Path);
    apiClone.SessionId = api.SessionId;
    var userDetails = String.Format("stuff {0}", u);
    var importResponse = apiClone.Post().WithString(userDetails, "application/json").Send(apiClone.SessionId).GetAwaiter().GetResult();

    if (importResponse.IsSuccessStatusCode) {
        var body = importResponse.Content.ReadAsStringAsync().GetAwaiter().GetResult().ToLower();

        if (body == "true") {
            return true;
        }
    }

    return false;
});

Again the above PLINQ query throws a Task Cancelled Exception after a couple of minutes, no other Exception is observed. Anyone ever had a PLINQ query be cancelled without writing the cancellation themselves or know why this might be?

starlight54
  • 1,051
  • 1
  • 14
  • 20
  • 3
    Why are you using a _compute_ programming API tailed for _CPU-bound_ tasks to perform I/O? PLINQ uses the thread pool to perform **compute operations** and all those threads are going to be **blocked** waiting on I/O. You might want to look at _TPL DataFlow_ instead –  Nov 21 '18 at 10:55
  • @MickyD I agree. I'd still like to pursue the question though, I just don't know why it's cancelling...maybe there is a hidden exception somewhere that never bubbles up? – starlight54 Nov 21 '18 at 11:06
  • Have a look at [Nesting await in Parallel.ForEach](https://stackoverflow.com/questions/11564506/nesting-await-in-parallel-foreach) – Liam Nov 21 '18 at 11:08
  • BTW best not to mix `Thread` and `Task`, so use `Task.Wait()` not `Thread.Sleep()` – Liam Nov 21 '18 at 11:09
  • Hmm I just tested it by blocking the `HttpClient`, the error that the `HttpClient` timed out never bubbles back out ;( So it's just that... – starlight54 Nov 21 '18 at 11:12
  • 1
    @starlight54 use an ActionBlock instead of PLINQ. PLINQ simply *doesn't* know about `async/await`. **Don't** create a new HttpClient each time either - HttpClient isn't just thread-safe, it's a lot faster to reuse it because it only has to perform DNS resolution, HTTPS channel establishment once – Panagiotis Kanavos Nov 21 '18 at 15:46
  • @starlight54 your current code starts an asynchronous call with `GetStringAsync` only to *block* it, so it can use PLINQ's DOP and make it async again. That's rather counterproductive. In fact, if you wanted to implement throttling you could batch requests and fire off as many GetStringAsync calls as there are items in the batch with eg `var tasks=batch.Select(num=>httpClient.GetStringAsync(...)); var results=await Task.WhenAll(tasks);`. – Panagiotis Kanavos Nov 21 '18 at 15:50

0 Answers0