I have a task that is looping reading on a socket:
private async void readLoop() {
while (!cts.IsCancelRequested()) {
await socket.ReceiveAsync(data, ...);
doWork(data);
}
}
And since the task need not run as soon as it is created, I'm using the Task
constructor, rather than Task.Run
.
private Task readTask = new Task(readLoop, cts, LongRunning);
// when the task need to run:
readTask.Start()
At this point it works fine, except that when the task need to finish, when I call readTask.Wait()
or await readTask
, the exceptions happened in ReceiveAsync
or doWork
are not attached to the readTask
. That means even there was exceptions, readTask.Status
is RunToComplete
and readTask.Exception
is null
. Accord. to the docs, this is due to the async method returns void, but when I tried this:
private async Task readLoop() {...}
It won't compile:
error CS0407: 'Task WebSocketInitiator.readLoop()' has the wrong return type
So is there anyway to make the task start at a later time and at the same time have it keep track of exceptions happened in the task?
A minimal test case to show the problem: https://dotnetfiddle.net/JIfDIn
If I understand you correctly you want to postpone running the Task until it is needed but you want to keep the creation in a constructor. That is fine, and there is a pattern used for these kind of situations. It's called Lazy
.
However, Lazy isn't async. Stephen Taub has however made an async implementation called AsyncLazy.
The reason why you got the error message that you got was because you didn't specify the return value for the outer Task that you created. Also you need to return foo
s value. If you wrap the call in a lambda which returns the inner Task you can get it though, as such:
var task1 = new Task<Task>(async () => await foo(),
CancellationToken.None, TaskCreationOptions.LongRunning);
task1.Start();
When you await it however you would await the outer task and not the task that it returns. Thus you need to Unwrap it:
task1.Unwrap().Wait();
This way you would catch the exception. However, this is probably not the best way, since the whole reason to use Task.Run, async and await was to avoid these constructs. In conclusion:
task1
- Kevin Gosse.Start()
can't be called. - Default
And since the task need not run as soon as it is created, I'm using the Task constructor, rather than Task.Run.
You should never use the task constructor. It has literally no rational use cases at all.
In your case, it sounds like you don't need a delegate or lazy-initialization, or anything like that; since your members and methods are private, it would make just as much sense to assign the task member at the time it executes:
private async Task readLoop();
// when the task needs to run:
readTask = Task.Run(() => readLoop());
However, if you do need to represent some code to run in the future and not now, then you want a delegate. In this case, an asynchronous delegate: Func<Task>
:
Func<Task> func = () => Task.Run(() => readLoop());
...
Task task = func();
async
method returnsvoid
, which prevents any calling code from observing the exception, because there's noTask
for the method which can represent the exception. You should not be usingasync void
in this context, and had you followed the recommended practice of not doing so, your problem would not happen. Why you get CS0407 when you change the return type I can't say; you must have made a mistake elsewhere as well, but you didn't provide a good Minimal, Complete, and Verifiable example that reliably reproduces the problem, so what that problem is, no one can say. - Peter Duniho