6

I'm trying to get into the asynchronous thing. I'd like to make one of my methods asynchronous because it takes a while to finish so I tried this:

public static async Task GenerateExcelFile(HashSet<string> codes, ContestViewModel model)
{
    var totalCodeToDistribute = model.NbrTotalCodes - (model.NbrCodesToPrinter + model.NbrCodesToClientService);
    if (model.NbrTotalCodes > 0)
    {
        using (var package = new ExcelPackage())
        {
                         
            await DoStuff(some, variables, here);
                        
            package.SaveAs(fileInfo);
        }
    }
}

So I could call it in my controller like that:

 await FilesGenerationUtils.GenerateExcelFile(uniqueCodesHashSet, model);

but when it comes to the "await" keyword, it says that "Type void is not awaitable"

Is that a way to await for void methods or is it not a best practice? And if so, what would be the best way to do it?

The controller:

[HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Index(ContestViewModel model)
        {
            var contentRootPath = _hostingEnvironment.ContentRootPath;

            DirectoryUtils.OutputDir = new DirectoryInfo(contentRootPath + Path.DirectorySeparatorChar
                                                                         + "_CodesUniques" + Path.DirectorySeparatorChar
                                                                         + model.ProjectName +
                                                                         Path.DirectorySeparatorChar
                                                                         + "_Codes");
            var directory = DirectoryUtils.OutputDir;

            var selectedAnswer = model.SelectedAnswer;

            var uniqueCodesHashSet = new HashSet<string>();

            try
            {
                
                while (uniqueCodesHashSet.Count < model.NbrTotalCodes)
                {
                    var generatedString = RandomStringsUtils.Generate(model.AllowedChars, model.UniqueCodeLength);
                    uniqueCodesHashSet.Add(generatedString.ToUpper());
                }

                #region FOR TXT FILES

                if (selectedAnswer == FileExtension.TXT.GetStringValue())
                {
                   await FilesGenerationUtils.GenerateTxtFiles(uniqueCodesHashSet, model, directory);
                }

                #endregion

                #region FOR XLSX FILES

                if (selectedAnswer == FileExtension.XLSX.GetStringValue())
                {
                    await FilesGenerationUtils.GenerateExcelFile(uniqueCodesHashSet, model);
                }

                #endregion
             
       
                return View();
            }
            catch (Exception ex)
            {
                Console.Write(ex);
            }

            return View();
        }

If I've understood what you're all saying, I must create a method that would be awaitable. Am I going right if I go with something like this:

public static Task DoStuff(ExcelWorksheet sheet, HashSet<string> codes, int rowIndex, int count, int maxRowValue)
        {
            foreach (var code in codes)
            {
                sheet.Row(rowIndex);
                sheet.Cells[rowIndex, 1].Value = code;
                rowIndex++;
                count++;
                if (rowIndex == maxRowValue && count < (codes.Count - 1))
                {
                    sheet.InsertColumn(1, 1);
                    rowIndex = 1;
                }
            }
            //What should be returned?!
            return null;
        }
TylerH
  • 20,799
  • 66
  • 75
  • 101
j0w
  • 505
  • 2
  • 12
  • 32
  • 1
    you have a return type specified of `Task`. – Daniel A. White Feb 28 '19 at 19:49
  • you must return an an awaitable object. – Daniel A. White Feb 28 '19 at 19:50
  • can you show the code for your controller method? – hunter Feb 28 '19 at 19:53
  • @hunter sure, i've juste edited the original post. (I've deleted some parts of it so i'ts a bit more readable) – j0w Feb 28 '19 at 19:57
  • 2
    The code you've shown does not return `void`; it returns `Task`. You should not be getting the error you're talking about with that code. – Heretic Monkey Feb 28 '19 at 19:58
  • @DanielA.White Well, how do I return an awaitable object if the method given by the EPPlus library is not? – j0w Feb 28 '19 at 19:58
  • 4
    Yeah, the issue is that `package.SaveAs` is not an async method, it can't be `awaited`. Put succinctly, your "DoStuff()" method needs to become async, and you need to await *that*. Generally speaking, the need to do that is going to drill down through all of your method calls until you find a call to a method that is async "out of the box", like something that works with a file or the network. – nlawalker Feb 28 '19 at 20:01
  • `I'd like to make one of my methods asynchronous because it takes a while to finish` - `async` doesn't mean "faster"; it means "does not block the calling thread". See "async is not a silver bullet" in my [async ASP.NET intro](https://msdn.microsoft.com/en-us/magazine/dn802603.aspx). – Stephen Cleary Mar 01 '19 at 01:08

2 Answers2

10

You can write async void methods but these cannot be awaited:

public static class Program
{
    public static async Task Main()
    {
        const int mainDelayInMs = 500;
        AsyncVoidMethod();
        await Task.Delay(mainDelayInMs);
        Console.WriteLine($"end of {nameof(Main)}");
    }

    static async void AsyncVoidMethod()
    {
        await Task.Delay(1000);
        Console.WriteLine($"end of {nameof(AsyncVoidMethod)}");
    }
}

As you can see AsyncVoidMethod is async but I cannot write await AsyncVoidMethod();.

Async void methods should (most of the time) not be used as you cannot wait for completion of the task and any exception thrown may not be handled(and so it can crash your application): Why exactly is void async bad?

asidis
  • 1,374
  • 14
  • 24
  • Thanks for your time @asidis. I've edited my original post to show in which direction I'm heading now that I've read you. Am i heading right ? if so, what should be returned by the new async DoStuff() method?... I'm struggling with those task thing I guess – j0w Feb 28 '19 at 20:31
  • 1
    I think the misconception here is that you can simply just decide to make any arbitrary method async, which is not the case. In most cases, asynchronicity "starts" with a call to a .NET base class library method that uses facilities in the OS to provide true asynchronicity. This is typically something that does I/O, like `FileStream.ReadAsync()`. A method that `awaits` on that call returns a `Task`, and other methods can then await on *that* method and also return `Task`s. This can propagate all the way through your app, such that the majority of your methods use and return `Task`s. – nlawalker Feb 28 '19 at 21:37
3

There is generally little to no benefit to pretending your method is async when it blocks under the hood. If you need a task you can wrap the blocking method in Task.Run to create an awaitable task. You will still be using and blocking a thread, just not the current one.

Recommended reading: https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-using.html

ScottyD0nt
  • 348
  • 2
  • 8