0

So I've got this bit of code that Visual Studio doesn't complain about:

int fortyTwo = await CalculateAnswerAsync();
Debug.WriteLine(fortyTwo);

async public static Task<int> CalculateAnswerAsync()
{
    await Task.Delay(1000);
    return 42;
}

I'm returning a task, which encapsulates the int 42 that I'm returning after 1 second and accessing via await. All great.

Now when I try to apply this methodology to an area of my program dealing with Lists, I get an error:

public List<Payee> Payees { get; set; } = new();

var payeesTask = dataService.GetPayees();
List<Payee> payees = await payeesTask;
Debug.WriteLine(payees.ToString());

public Task<List<Payee>> GetPayees()
{
    //Payees is underlined with the red squiggle, boooo
    if(Payees.Count != 0) return Payees; 
    Payees.Add(new Payee { Id = 0, Name = "Food Lion", DefaultCategoryId = 0, DefaultIsCredit = false });
    return Payees;
}

The error I get is Cannot implicitly convert type 'System.Collections.Generic.List<MyApp.Model.Payee>' to 'System.Threading.Tasks.Task<System.Collections.Generic.List<MyApp.Model.Payee>>'

Which I totally understand and agree with, except that my example with returning 42 doesn't error out. What gives?

XJonOneX
  • 305
  • 2
  • 11
  • 2
    Your code doesn't have an async call so it doesn't need to be async. If it MUST be async, then you just need to change "public Task<..." to "public async Task<..." but you'll get a warning because there's no async code inside. You're risking unnecessary perf penalty that way. Why do you need it to be async? – Sedat Kapanoglu Nov 17 '22 at 22:42
  • Adding async fixed it. The list.add is placeholder code for SQLite implementation later on down the road. – XJonOneX Nov 17 '22 at 22:45
  • Also, am I misunderstanding something about async and await? I thought the point of it all was to concurrently execute code, so to speak. While my data is being added manually to this list, I can have the UI be processed. – XJonOneX Nov 17 '22 at 22:47
  • 4
    @XJonOneX it does indeed looks like you are completely misunderstanding what `async` means. You should read on it whatever info you find useful for you. TL;DR: no, it does not make execution asynchronous by itself. (example article to read - https://stackoverflow.com/questions/27265818/does-the-use-of-async-await-create-a-new-thread) – Alexei Levenkov Nov 17 '22 at 22:52
  • @XJonOneX Async is just an easier way to write I/O callbacks. Async can be used for lightweight concurrency too, but that kind of concurrency relies on I/O-driven tasks. So, it doesn't make sense to make a non-I/O function async unless you're forced to implement an API. I explain the difference in my book Street Coder. – Sedat Kapanoglu Nov 18 '22 at 17:06

1 Answers1

1

Short form: the GetPayees() method declaration is missing the async keyword.

Yeah, that's more of a comment. Let's see if I can expand on that a little...

The async keyword in a method definition causes the compiler to do a bit of work to create a task around your code: an IAsyncStateMachine implementation and code to create instances of that class, initialize it and create a Task<...> object to return. It's all quite interesting to look at, if a little convoluted.

Since you haven't declared the method as async none of that gets done. Instead you've declared a simple method that expects you to return a Task<List<Payee>> that you have manually created, or sourced elsewhere. Instead you're trying to return a raw List<Payee> without wrapping a task around it.

So the simple fix is to add async to your (currently synchronous) code:

public async Task<List<Payee>> GetPayees() 
{
    // need to await something or the compiler complains
    await Task.Delay(1);
    // rest of your code here...
}

If you're just mocking something that is properly asynchronous in live code you can use Task.FromResult() to return a cut-down task that just holds a return value, or if you need to do something more interesting then you could wrap synchronous code in Task.Run() to have it run on a separate thread.

Task.FromResult():

public Task<List<Payee>> GetPayees()
{
    if(Payees.Count == 0)
        Payees.Add(new Payee { Id = 0, Name = "Food Lion", DefaultCategoryId = 0, DefaultIsCredit = false });
    return Task.FromResult(Payees);
}

Task.Run:

public Task<List<Payee>> GetPayees()
{
    return Task.Run(() => 
    {
        // long-running synchronous code here
        Thread.Sleep(250);
        
        if(Payees.Count == 0)
            Payees.Add(new Payee { Id = 0, Name = "Food Lion", DefaultCategoryId = 0, DefaultIsCredit = false });
        return Task.FromResult(Payees);
    };
}

Assuming you're not testing how your code handles delays in fetching the payee list, Task.FromResult() is probably the better option.

(Incidentally... am I the only person who finds the idea of 'food lions' a little weird here?)

Corey
  • 15,524
  • 2
  • 35
  • 68