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 | Show 5 more comments1 Answer
Reset to default 0I'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
Console.ReadKey
before the firstawait
? That would mean the async/await state machine doesn't return aTask
until the firstawait
is reached? I'm not sure that's correct, because some stuff happens before it hangs... – Mark Commented Mar 6 at 19:10await
of "truly synchronous" operation. Try addingawait Task.Yield()
orawait Task.Delay(1)
before yourwhile
loop. – Guru Stron Commented Mar 6 at 21:28