-3

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


2 답변


2

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 foos 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:

  • Go for AsyncLazy if you need to postpone the calling of your Task
  • Don't call the Task constructor
  • Use Task.Run


  • This really helps, thanks! - fluter
  • Just nitpicking, but it's probably better to unwrap it before storing it in task1 - Kevin Gosse
  • @KevinGosse That would have been nice, but then .Start() can't be called. - Default
  • @Default Good point, my bad - Kevin Gosse

2

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();

Linked


Related

Latest