c# - Why does Task.Factory.StartNew for an async lambda work, when calling the lambda directly does not? - Stack Overflow

I'm writing a console application that needs to copy console input one keypress at a time to a soc

I'm writing a console application that needs to copy console input one keypress at a time to a socket (while also doing other things). In order to do that, I figure I have a couple options. In the before times, I would've spun up a thread to do the work, but for this particular implementation, I've decided to use a Task instead. This has led to some confusion on my part.

I have this simple function:

private static async Task ReadConsoleInput(Socket socket, Encoding encoding, CancellationToken cancellationToken)
{
    var inputCharacter = new char[1]; // we only input one character at a time
    var convertedBytes = new byte[4]; // four should be enough for anyone!

    while (!cancellationToken.IsCancellationRequested) {
        var readKey = Console.ReadKey(true);

        inputCharacter[0] = readKey.KeyChar;

        var byteCount = encoding.GetBytes(inputCharacter, convertedBytes);

        await socket.SendAsync(convertedBytes.AsMemory(0, byteCount), cancellationToken);
    }
}

If I start a Task for this in this way:

 var consoleReadTask = Task.Factory.StartNew(
    async () => await ReadConsoleInput(socket, encoding, tokenSource.Token),
    tokenSource.Token,
    TaskCreationOptions.None,
    TaskScheduler.Default);

Everything functions correctly. This seems quite odd to me, however, because ReadConsoleInput, being async, already returns a Task. Thus, I figured it would be exactly the same to start the Task in this way:

var consoleReadTask = ReadConsoleInput(socket, encoding, tokenSource.Token);

This turned out to be incorrect, however. My program hangs (somewhere else, I haven't looked into it, because that's not the point here) before I ever read any input.

To me, it seems like these two methods are functionally similar, if not identical. The Task.Factory.StartNew implementation seems weird, because normally one wouldn't start a Task with an async lambda (right? am I crazy?), but both should function, I'd think.

Why are these two implementations different?

I'm writing a console application that needs to copy console input one keypress at a time to a socket (while also doing other things). In order to do that, I figure I have a couple options. In the before times, I would've spun up a thread to do the work, but for this particular implementation, I've decided to use a Task instead. This has led to some confusion on my part.

I have this simple function:

private static async Task ReadConsoleInput(Socket socket, Encoding encoding, CancellationToken cancellationToken)
{
    var inputCharacter = new char[1]; // we only input one character at a time
    var convertedBytes = new byte[4]; // four should be enough for anyone!

    while (!cancellationToken.IsCancellationRequested) {
        var readKey = Console.ReadKey(true);

        inputCharacter[0] = readKey.KeyChar;

        var byteCount = encoding.GetBytes(inputCharacter, convertedBytes);

        await socket.SendAsync(convertedBytes.AsMemory(0, byteCount), cancellationToken);
    }
}

If I start a Task for this in this way:

 var consoleReadTask = Task.Factory.StartNew(
    async () => await ReadConsoleInput(socket, encoding, tokenSource.Token),
    tokenSource.Token,
    TaskCreationOptions.None,
    TaskScheduler.Default);

Everything functions correctly. This seems quite odd to me, however, because ReadConsoleInput, being async, already returns a Task. Thus, I figured it would be exactly the same to start the Task in this way:

var consoleReadTask = ReadConsoleInput(socket, encoding, tokenSource.Token);

This turned out to be incorrect, however. My program hangs (somewhere else, I haven't looked into it, because that's not the point here) before I ever read any input.

To me, it seems like these two methods are functionally similar, if not identical. The Task.Factory.StartNew implementation seems weird, because normally one wouldn't start a Task with an async lambda (right? am I crazy?), but both should function, I'd think.

Why are these two implementations different?

Share Improve this question asked Mar 6 at 18:53 MarkMark 11.8k12 gold badges67 silver badges99 bronze badges 10
  • maybe your program depends on the first console.readkey not being on the same thread that executes the non-taskfactory method? – Ivan Petrov Commented Mar 6 at 19:04
  • 1 You mean because I call the blocking Console.ReadKey before the first await? That would mean the async/await state machine doesn't return a Task until the first await is reached? I'm not sure that's correct, because some stuff happens before it hangs... – Mark Commented Mar 6 at 19:10
  • that would be my intuition - yes. – Ivan Petrov Commented Mar 6 at 19:16
  • Is there something about Console I/O in Windows that would cause this issue? – Mark Commented Mar 6 at 19:34
  • "That would mean the async/await state machine doesn't return a Task until the first await is reached" - that's exactly how async methods work, they are executed synchronously until first await of "truly synchronous" operation. Try adding await Task.Yield() or await Task.Delay(1) before your while loop. – Guru Stron Commented Mar 6 at 21:28
 |  Show 5 more comments

1 Answer 1

Reset to default 0

I'm unable to comment but would like to share an opinion:

Task.Factory.StartNew is queueing the callback on to the ThreadPool. This can be seen if one looks at the source code:

  • StartNew

    • https://source.dot/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskFactory.cs,d107c03204fc34e1
  • internal StartNew

    • https://source.dot/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs,ca049b8b7d014a0d
  • ScheduleAndStart

    • https://source.dot/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs,107ac97251bea153

Which means that the Console.ReadKey is running in the background thread. This is interesting because the Task will not be started immediately, which gives time for other stuff to happen in the mean time (unlike the direct approach).

The main difference between these two is that the former one will continue executing while the latter will block until a key is read (in both examples the Task is not awaited).

Calling the ReadConsoleInput directly is what is causing the issue, here's why I think that: somewhere before ReadConsoleInput is called you are calling some other un-awaited async Console method which does some kind of reading (Console.In.[...], Console.Out.[...] etc.) or something else Console related.

Both Console.ReadKey and the other method are in a deadlock because both are locking on themselves:

  • https://source.dot/#System.Console/System/IO/SyncTextReader.Unix.cs,ebee0a78f846715f

  • https://source.dot/#System.Console/System/IO/SyncTextReader.cs,21ee17a8c2ef18cb,references

Reason why starting the process with TaskFactory is working is because the other method completed its work or got disposed.

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744955594a4603174.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信