Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Task.ContinueWith execution order

Apparently, I'm not understanding how to use the ContinueWith method. My goal is to execute a task, and when complete, return a message.

Here's my code:

    public string UploadFile()
    {
        if (Request.Content.IsMimeMultipartContent())
        {
            //Save file
            MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath("~/Files"));
            Task<IEnumerable<HttpContent>> task = Request.Content.ReadAsMultipartAsync(provider);

            string filename = "Not set";

            task.ContinueWith(o =>
            {
                //File name
                filename = provider.BodyPartFileNames.First().Value;
            }, TaskScheduler.FromCurrentSynchronizationContext()); 

            return filename;
        }
        else
        {
            return "Invalid.";
        }
    }

The variable "filename" always returns "Not set". It seems the code within the ContinueWith method is never called. (It does get called if I debug through it line by line in VS.)

This method is being called in my ASP.NET Web API controller / Ajax POST.

What am I doing wrong here?

like image 564
Rivka Avatar asked May 08 '12 16:05

Rivka


2 Answers

If you're using an asynchronous operation, the best approach would be to make your operation asynchronous as well, otherwise you'll lose on the advantages of the async call you're making. Try rewriting your method as follows:

public Task<string> UploadFile()
{
    if (Request.Content.IsMimeMultipartContent())
    {
        //Save file
        MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath("~/Files"));
        Task<IEnumerable<HttpContent>> task = Request.Content.ReadAsMultipartAsync(provider);

        return task.ContinueWith<string>(contents =>
        {
            return provider.BodyPartFileNames.First().Value;
        }, TaskScheduler.FromCurrentSynchronizationContext()); 
    }
    else
    {
        // For returning non-async stuff, use a TaskCompletionSource to avoid thread switches
        TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
        tcs.SetResult("Invalid.");
        return tcs.Task;
    }
}
like image 172
carlosfigueira Avatar answered Oct 02 '22 10:10

carlosfigueira


The reasons for your variable not being set are:

  • the tasks are instantiated, but not run.
  • even if the tasks ran, the function would probably return before they finished running so, it would still return "Not set". The fix for this is waiting for the final task (the one setting fileName) to finish.

Your code could be fixed like this:

public string UploadFile()
{
    if (Request.Content.IsMimeMultipartContent())
    {
        //Save file
        MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath("~/Files"));
        Task<IEnumerable<HttpContent>> task = Request.Content.ReadAsMultipartAsync(provider);

        string filename = "Not set";

        var finalTask = task.ContinueWith(o =>
            {
                //File name
                filename = provider.BodyPartFileNames.First().Value;
            }, TaskScheduler.FromCurrentSynchronizationContext()); 

        task.Start();

        finalTask.Wait();

        return filename;
    }
    else
    {
        return "Invalid.";
    }
}

The additions are the following:

  • assigned the return value of task.ContinueWith to a variable called finalTask. We need this task, because we'll wait for it to finish
  • started the task (the task.Start(); line)
  • waited for the final task to finish before returning (finalTask.Wait();)

If possible, please consider not implementing this asynchronously, because in the end it's synchronous (you're waiting for it to finish) and the current implementation adds complexity that could probably be avoided.

Consider doing something along these lines (if possible):

public string UploadFile()
{
    if (Request.Content.IsMimeMultipartContent())
    {
        //Save file
        MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath("~/Files"));

        Request.Content.ReadAsMultipart(provider); // don't know if this is really valid.

        return provider.BodyPartFileNames.First().Value;
    }
    else
    {
        return "Invalid.";
    }
}

Disclaimer: I have not actually executed the above code; I just wrote it to illustrate what should be done.

like image 28
Cristian Lupascu Avatar answered Oct 02 '22 10:10

Cristian Lupascu