Reputation: 2409
To get a sense for how C#'s threading constructs work, I've written a simple test program that performs the same task - sleeping for 3 seconds then returning an object - both synchronously and asynchronously. I've set up some stopwatches to get a better sense as to how the code is flowing.
The synchronous call is working as expected: there's a 3 sec delay between the "before sync" and "after sync" prints. However, for the async call, the program hangs indefinitely after "after async". I'm expecting "before async" and "after async" to print quickly in succession (since ExecuteAsync
just returns a Task), then for "awaited async" to print three seconds later (or rather, at least three seconds later; if there was a ton of logic between ExecuteAsync
and when that Task is awaited, it might be longer).
namespace TaskTest
{
class Program
{
static void Main(string[] args)
{
doStuff().Wait();
Console.WriteLine("Press any key to end");
Console.ReadKey();
}
static async Task doStuff()
{
TestClass tceOn = new TestClass(true);
Stopwatch s = Stopwatch.StartNew();
s.Checkpoint("Before sync on");
tceOn.ExecuteSync();
s.Checkpoint("After sync on");
Console.WriteLine();
s.Checkpoint("Before async on");
Task<Foo> fooTask = tceOn.ExecuteAsync();
s.Checkpoint("After async on");
Foo foo = await fooTask;
s.Checkpoint("Awaited async on");
}
}
class TestClass
{
public bool ShouldWait = false;
public TestClass(bool tce)
{
ShouldWait = tce;
}
public Task<Foo> ExecuteAsync()
{
Task<Foo> output;
RunExecute(out output, true);
return output;
}
public Foo ExecuteSync()
{
Task<Foo> dud;
return RunExecute(out dud);
}
private Foo RunExecute(out Task<Foo> task, bool async = false)
{
Foo outputFoo;
if(async)
{
task = new Task<Foo>(makeFoo);
outputFoo = null;
}
else
{
task = null;
outputFoo = makeFoo();
}
return outputFoo;
}
private Foo makeFoo()
{
if (ShouldWait)
Thread.Sleep(3000);
return new Foo();
}
}
class Foo { }
}
To clean it up a bit, I didn't paste the extension method for Stopwatch's Checkpoint()
method; it just prints the number of ticks so I can get a sense for the time elapsed.
So, why isn't this code working as expected? I was hoping this would be a "simplest thing that could possibly work"-type scenario. Thanks!
Update:
Having changed my code according to Peter Duniho's first suggestion (task = Task.Run(() => makeFoo());
), I'm now trying to figure out how to get this to work with a .ContinueWith()
block:
private Foo RunExecute(out Task<Foo> task, bool async = false)
{
Foo outputFoo;
if(async)
{
task = Task.Run(() => makeFoo()).ContinueWith((t) => // **** error here ****
{
outputFoo = null;
});
}
else
{
task = null;
outputFoo = makeFoo();
}
return outputFoo;
}
This is giving me an implicit conversion error on the line where I made the change:
Cannot implicitly convert type 'System.Threading.Tasks.Task' to 'System.Threading.Tasks.Task<TaskTest.Foo>'
Because I'm new to how Tasks, Actions, and the like work, I'm not quite sure what the problem is/what's not matching up.
Upvotes: 0
Views: 519
Reputation: 457217
To get a sense for how C#'s threading constructs work, I've written a simple test program that performs the same task - sleeping for 3 seconds then returning an object - both synchronously and asynchronously.
Your actual code is quite complex. So let's start with the "simplest possible" 3-second synchronous sleep:
class Program
{
static void Main(string[] args)
{
TestClass tceOn = new TestClass();
Stopwatch s = Stopwatch.StartNew();
s.Checkpoint("Before sync on");
tceOn.Execute();
s.Checkpoint("After sync on");
Console.WriteLine("Press any key to end");
Console.ReadKey();
}
}
class TestClass
{
public Foo Execute()
{
Thread.Sleep(3000);
return new Foo();
}
}
class Foo { }
Now, to create an asynchronous equivalent, you first start at the "leaves" - in this case, Thread.Sleep
in TestClass.Execute
, and work your way up. This is the natural way to convert code to be asynchronous (except in this case, we're creating the asynchronous code side-by-side instead of in-place). The first step is always to identify blocking operations (Thread.Sleep
) and discover asynchronous equivalents (in this case, Task.Delay
):
class TestClass
{
public async Task<Foo> ExecuteAsync()
{
await Task.Delay(3000);
return new Foo();
}
public Foo Execute()
{
Thread.Sleep(3000);
return new Foo();
}
}
Note the similarities between the two methods. Also note that there is no need for Task.Run
. This is the case for all naturally-asynchronous code.
At this point, you're ready to add the asynchronous test. Since Main
can't be async
, this is the point at which you need to move the test code to another method:
class Program
{
static void Main(string[] args)
{
MainAsync().Wait();
Console.WriteLine("Press any key to end");
Console.ReadKey();
}
static async Task MainAsync()
{
TestClass tceOn = new TestClass();
Stopwatch s = Stopwatch.StartNew();
s.Checkpoint("Before sync on");
tceOn.Execute();
s.Checkpoint("After sync on");
s.Checkpoint("Before async on");
Task<Foo> fooTask = tceOn.ExecuteAsync();
s.Checkpoint("After async on");
Foo foo = await fooTask;
s.Checkpoint("Awaited async on");
}
}
For your final question regarding ContinueWith
, the answer is "don't use ContinueWith
; use await
instead".
Upvotes: 0
Reputation: 70701
You haven't actually run any Task
. So you're waiting on something that will never complete.
To fix your code exactly as it is, you can do this:
private Foo RunExecute(out Task<Foo> task, bool async = false)
{
Foo outputFoo;
if(async)
{
task = Task.Run(() => makeFoo());
outputFoo = null;
}
else
{
task = null;
outputFoo = makeFoo();
}
return outputFoo;
}
A more idiomatic way would look something like this:
private Foo RunExecute(out Task<Foo> task, bool async = false)
{
Foo outputFoo;
if(async)
{
task = makeFooAsync();
outputFoo = null;
}
else
{
task = null;
outputFoo = makeFoo();
}
return outputFoo;
}
async Task<Foo> makeFooAsync()
{
await Task.Delay(3000);
return new Foo();
}
You can even change the example so that both the synchronous and asynchronous work exactly the same (and don't require the out
parameter):
private Task<Foo> RunExecute(bool async = false)
{
Foo outputFoo;
if(async)
{
return makeFooAsync();
}
else
{
return Task.FromResult(makeFoo());
}
}
Upvotes: 2