Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handle "A task was canceled" exception

Tags:

c#

async-await

My C# app uploads file to some API, I'm using multipart request, i.e I'm uploading a json string and binary contect of the file, it works fine for most files, but for very few it gives exception, I mean let's try for file named 50MB.zip I'm getting the exception:

A task was canceled. :::  ::: System.Threading.Tasks.TaskCanceledException: A task was canceled.
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()

My code looks roughly as follows:

public async Task<Dictionary<string , string>> Upload(string filePath)
{   
    FileInfo fi = new FileInfo(FilePath);
    string jsonString="some json string";
    byte[] fileContents=File.ReadAllBytes(fi.FullName);
    Uri webService = new Uri(url);
    HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post , webService);
    requestMessage.Method = HttpMethod.Post;
    requestMessage.Headers.Add("Authorization" , "MyKey1234");
    const string boundry = "------------------My-Boundary";
    MultipartFormDataContent multiPartContent = new MultipartFormDataContent(boundry);
    ByteArrayContent byteArrayContent = new ByteArrayContent(fileContents);
    multiPartContent.Add(byteArrayContent);
    requestMessage.Content = multiPartContent;
    HttpClient httpClient = new HttpClient();
    HttpResponseMessage httpResponse = await httpClient.SendAsync(requestMessage , HttpCompletionOption.ResponseContentRead , CancellationToken.None);
    //exception in this line ^
    return new Dictionary<string , string>();
}

The caller:

myDictionary = await Upload(filePath);

The structure of the console app is as follows:

class Program
{
    static void Main(string[] args)
    {
        MainAsync().Wait();
    }

    static async Task MainAsync()
    {
        new MyClass().Start();
    }
}

And inside MyClass:

public async void Start()
{
    myDictionary = await Upload(filePath);
}

I guess I'm not using async correctly, can you maybe see what I'm missing? any ideas?

like image 935
exe Avatar asked Jul 30 '18 12:07

exe


People also ask

What is a task canceled exception?

TaskCanceledException(String, Exception, CancellationToken) Initializes a new instance of the TaskCanceledException class with a specified error message, a reference to the inner exception that is the cause of this exception, and the CancellationToken that triggered the cancellation.

What is using system threading tasks in C#?

Provides types that simplify the work of writing concurrent and asynchronous code. The main types are Task which represents an asynchronous operation that can be waited on and cancelled, and Task<TResult>, which is a task that can return a value.


1 Answers

I'm 99% sure this error is due to a timeout, or the fact you don't actually await your Start method in MainAsync

I've addressed the timeout issue in the follow code along with some other small changes, which don't necessarily answer your question but hopefully help you nevertheless

class Program
{
    private static HttpClient httpClient;

    static void Main(string[] args)
    {
        httpClient = new HttpClient();
        httpClient.BaseAddress = new Uri("your base url");
        // add any default headers here also
        httpClient.Timeout = new TimeSpan(0, 2, 0); // 2 minute timeout

        MainAsync().Wait();
    }

    static async Task MainAsync()
    {
        await new MyClass(httpClient).StartAsync();
    }
}

What I've done here is move the HttpClient out from your Upload() method because this class is designed to be reused many times. I've passed the httpClient object to MyClass's constructor which you'll see in the next code snippet.

I've also changed MainAsync() to await the StartAsync (renamed from Start to StartAsync because it's convention to suffix async methods) because in your original code MainAsync() wasn't actually awaiting anything

As I mentioned in the comments, you can change MainAsync().Wait() to await MainAsync() if you change Main to be static async Task Main, which will require you to change your build language to C# 7.1 or above

public class MyClass
{
    private Dictionary<string, string> myDictionary;
    private readonly HttpClient httpClient;

    public MyClass(HttpClient httpClient)
    {
        this.httpClient = httpClient;
    }

    public async Task StartAsync()
    {
        myDictionary = await UploadAsync("some file path");
    }

    public async Task<Dictionary<string, string>> UploadAsync(string filePath)
    {
        byte[] fileContents;
        using (FileStream stream = File.Open(filePath, FileMode.Open))
        {
            fileContents = new byte[stream.Length];
            await stream.ReadAsync(fileContents, 0, (int)stream.Length);
        }

        HttpRequestMessage requestMessage = new HttpRequestMessage();
        // your request stuff here

        HttpResponseMessage httpResponse = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, CancellationToken.None);

        // parse response and return the dictionary
    }
}

In MyClass I've made the following changes

Added a HttpClient parameter to the class' constructor, so we can pass our global HttpClient object to this class for it to reuse (which is what we do in MainAsync())

As mentioned before, I've renamed Start and Upload to StartAsync and UploadAsync because it's good practice to suffix async methods with Async

Start was changed from void to a Task because you should only use async void for event handlers

I changed the way your file is read to also be async, because it seems wasteful to have an async method that then blocks the CPU waiting for File.ReadAllBytes to finish. Wherever possible you should use async/await for I/O.

like image 152
p3tch Avatar answered Oct 19 '22 18:10

p3tch