0

I have a method I'm calling and I want to make sure it absolutely finishes before continuing. I'm also interested in knowing if it completed successfully or not, so I'm doing the following:

async void Foo() {
  bool success;

  // stuff

  await Task.Run(
    () => Bar(out success)
  );

  if (!success) { // this is the line causing the compiler-error
    // handle
  }

  // other stuff
}

void Bar(out bool success);

But I'm getting error

CS0165 Use of unassigned local variable 'success'

This isn't a huge deal, as I can get cute and initialize success=false and pass it as ref instead of out and that seems to work as desired. However, I'm curious about what the intricacies of async-await (or Task.Run) are that seem to lead to a case that doesn't guarantee out success will be properly assigned.

Any enlightenment is appreciated!

Edit:

To add a bit more context, the following block compiles and executes fine.

void Caller() {
  bool success;
  Callee(out success);
  if (success) {
    // do something
  }
}

void Callee();

This is because out parameters are not required to be initialized before being passed. For more on out: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/out-parameter-modifier

  • Using uninitialized local variables is not allowed in C#. https://stackoverflow.com/questions/9233000/why-compile-error-use-of-unassigned-local-variable – Scuba Steve Apr 26 '18 at 00:35
  • 2
    https://stackoverflow.com/questions/8931226/are-c-sharp-uninitialized-variables-dangerous/8933935#8933935 – Scuba Steve Apr 26 '18 at 00:37
  • I know what the error is and how to prevent it. Please read the whole question. – J. Arrillaga Apr 26 '18 at 00:39
  • 6
    It's not async causing the error, it's the fact that you have a closure over success; `Action t = () => { Bar(out success);};` causes the same error – JohanP Apr 26 '18 at 00:40
  • " I can get cute and initialize success=false" Initializing the variable fixes it. Because C# doesn't let you use uninitialized locals. The why is in the two links above, read them. – Scuba Steve Apr 26 '18 at 00:42
  • 2
    @ScubaSteve That's not really what the question is asking. This is more about why is a variable still considered unitialized if the initialization would take place in a closure. – Jonathon Chase Apr 26 '18 at 00:45
  • 1
    I think the issue is the Lambda, not the async. Lambdas "capture" variables the momenty they are created. But capture is a form of using, and how can you use something not initialised? Especially with Multitasking (a common use of Lambdas) capturing becomes really important. – Christopher Apr 26 '18 at 00:49
  • I'm assuming that inside the closure there's some other work for the compiler to do that causes this failure. That'd be my guess. – Scuba Steve Apr 26 '18 at 00:49
  • @ScubaSteve It's not that much different from this absolutely fine-to-compile code, so I can see why someone might ask. `bool success; Bar(out success); if(success);` – Jonathon Chase Apr 26 '18 at 00:50
  • Strip out the @JonathonChase - From the OP's original comment, that if he initializes the variable first it works fine. I'm not sure yours would work either (though I haven't run the test). – Scuba Steve Apr 26 '18 at 00:52
  • @ScubaSteve `bool success; Bar(out success); if(success);` is perfectly fine code, compiler doesn't complain. – JohanP Apr 26 '18 at 00:55
  • So it has something to do with how the compiler is building that closure. Perhaps it sees that whole construct as a separate method, goes to pass the local by value, and fails there. – Scuba Steve Apr 26 '18 at 00:58
  • 4
    From the compiler’s perspective, it can’t be sure that the delegate passed to Task.Run is actually going to be called by Task.Run before the method returns. Yes we know that it will because we know how Task.Run is supposed to work and we assume it is free of any bugs, but what if MSFT implemented it incorrectly such that in some rare or bizarre circumstances Task.Run doesn’t actually run the supplied delegate and returns without throwing any exceptions. If that’s possible (and it is) then it’s possible for your code to reach the if statement without success being initialized. – Dave M Apr 26 '18 at 01:02
  • I believe if you initialise the variable you can still use `out` instead of `ref` - https://dotnetfiddle.net/Y84MtT – stuartd Apr 26 '18 at 01:05

1 Answers1

6

Consider the following possible (though totally incorrect) implementation of Task.Run:

public class Task
{
    public static Task Run(Action action)
    {
        return Task.Delay(500);
    }
    //...
}

Now think about the code in question. With this implementation of Task.Run, will the Bar method ever get called? No. Therefore success will never get initialized, and an attempt to access an uninitialized variable is made.

The compiler has no idea about the real implementation of Task.Run or what it will do. It can’t assume that it isn’t implemented like I have above, and therefore an uninitialized variable is possible.

Dave M
  • 2,863
  • 1
  • 22
  • 17