0

I have a lambda expression with an async call inside

public async Task UploadFile(string id)
{
    Progress<double> progress = new Progress<double>(async x =>
    {
        await Clients.Client(id).SendAsync("FTPUploadProgress", x);
    });
    await client.DownloadFileAsync(localPath, remotePath, true, FluentFTP.FtpVerify.Retry, progress);
}

I want to call the async method when progress is changed.

I'm getting the following warning:

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API call

How can I make this method asynchronous?

Should I rewrite the lambda with a System.Action<T> class?

11
  • 2
    UploadFile is marked as async yet there's not a single await in it Commented Sep 21, 2018 at 12:49
  • 2
    Why do you have the SendAsync call inside the progress delegate? You should move that into the outer method. Then if you want to use the progress, you should pass it into a method that accepts a Progress parameter and implement the progress logic in the lambda expression. Commented Sep 21, 2018 at 12:49
  • 1
    I would have expected an IProgress argument passed to SendAsync ... similar as you would a CancellationToken. Commented Sep 21, 2018 at 12:50
  • 1
    "I want to call the method when progress is changed" You should add this to your question. That's a whole different game. Commented Sep 21, 2018 at 12:52
  • 2
    @myro Because there was no await operator as the warning states. An async method needs to have an await operator, otherwise, it's not asynchronous. The one you had inside the progress delegate does not belong to it. Commented Sep 21, 2018 at 12:58

2 Answers 2

4

Use an event handler and raise it in the progress action. the event handler will allow async calls.

Luckily Progress<T> has a ProgressChanged event that you can subscribe to.

Review the following example based on the presented code in original problem

public async Task UploadFile(string id) {
    EventHandler<double> handler = null;
    //creating the handler inline for compactness
    handler = async (sender, value) => {
        //send message to client asynchronously
        await Clients.Client(id).SendAsync("FTPUploadProgress", value);
    };
    var progress = new Progress<double>();
    progress.ProgressChanged += handler;//subscribe to ProgressChanged event

    //use the progress as you normally would
    await client.DownloadFileAsync(localPath, remotePath, true, FluentFTP.FtpVerify.Retry, progress);

    //unsubscribe when done 
    progress.ProgressChanged -= handler;
}

So now when progress is reported, the event handler can make the asynchronous call.

Reference Async/Await - Best Practices in Asynchronous Programming

Another option would be to create you own IProgress<T> implementation that takes a Func<Task<T>> that would allow for async calls, but that might be overkill.

Sign up to request clarification or add additional context in comments.

3 Comments

Seems a little bit convoluted to me. Is there a reason not to use the CTOR that takes an Action directly?
@Fildor the action will be an async void which is usually not advised. Event handlers are the only exception to that rule about async void. Check linked article in answer.
AH, now I see. I totally missed the void.
2

I think you missunderstood the usage of the Progress<T> class. The Compiler complains, that your Method UploadFile lacks an await operator. Your lambda would execute asynchronous, when it would be called.

So heres a short Summary how to use the IProgressyT> interface:

If you have a Method that should support reporting of Progress, it can take an IProgress<T> as an Parameter and communicate its porgress through this object. It does not perform the monitored Operation. The Lambda you supply is executed each time the Report() Method is called on the Progress<T>. This Lambda usually is used to update the UI.

Here is an example for that.

public async Task DoStuff(IProgress<double> progress = null)
{
     for(int i = 0; i < 100; ++i)
     {
         await Task.Delay(500);
         progress?.Report((double)(i +1) / 100);
     }
}


// somewhere else in your code
public void StartProgress(){
    var progress = new Progress(p => Console.WriteLine($"Progress {p}"));
    DoStuff(progress);  
}

2 Comments

Microsoft actually recommends progress?.Report((double)(i +1) / 100); (treat progress so that it can be null)
@Fildor I'm surprised i forgot the '?.' here. I'll change the answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.