This is a Windows forms application.
this.Server.GetLogMessages()
will block if there are no messages to get, so I want this in a separate thread. I'm putting the messages into a ConcurrentStack
and then pulling them out on the main thread to output them.
Here is the respective code:
private async Task UpdateLogs()
{
var logMessages = new ConcurrentStack<string>();
// assign to `_` to avoid warning
var _ = Task.Run(async () =>
{
await foreach (var message in this.Server.GetLogMessages())
{
logMessages.Push(message);
}
}, this.CancellationTokenSource.Token);
await foreach (var message in GetMessages())
{
// don't update the UI if task is canceled
if (this.CancellationTokenSource.Token.IsCancellationRequested)
{
break;
}
this.textLogs.AppendText($"{message}{Environment.NewLine}");
}
async IAsyncEnumerable<string> GetMessages()
{
var message = String.Empty;
while (!this.CancellationTokenSource.IsCancellationRequested)
{
while (!logMessages.TryPop(out message))
{
await Task.Delay(100);
}
yield return message;
}
}
}
It works fine, but seems as if it could be better/cleaner somehow.
-
1\$\begingroup\$ this.Server.GetLogMessages() will block if there are no messages to get means that the method is not properly implemented. Can it be fixed? \$\endgroup\$aepot– aepot2020年09月01日 23:52:19 +00:00Commented Sep 1, 2020 at 23:52
-
\$\begingroup\$ Have you considered to use DataFlow for this problem? \$\endgroup\$Peter Csala– Peter Csala2020年09月03日 10:24:00 +00:00Commented Sep 3, 2020 at 10:24
-
1\$\begingroup\$ @aepot It may be able to be fixed. That is a different issue though. \$\endgroup\$rhughes– rhughes2020年09月03日 12:26:49 +00:00Commented Sep 3, 2020 at 12:26
-
\$\begingroup\$ @PeterCsala I have not heard of that. What is it? \$\endgroup\$rhughes– rhughes2020年09月03日 12:27:03 +00:00Commented Sep 3, 2020 at 12:27
-
1\$\begingroup\$ @aepot It seems so. I haven't tested it yet sorry. I will let you know. \$\endgroup\$rhughes– rhughes2020年09月04日 23:25:46 +00:00Commented Sep 4, 2020 at 23:25
1 Answer 1
My try to simplify and prettify the method.
Avoid using globals in async code, pass Token as argument.
// optimized out async State Machine
private Task UpdateLogsAsync(IProgress<string> status, CancellationToken token) => Task.Run(async () =>
{
await foreach (var message in this.Server.GetLogMessages().WithCancellation(token))
{
status.Report(message);
}
}, token);
Usage
// synchronized callback. Create new in UI Thread and its body will be always executed there.
IProgress<string> status = new Progess<string>(message =>
{
this.textLogs.AppendText($"{message}{Environment.NewLine}");
});
try
{
await UpdateLogsAsync(status, this.CancellationTokenSource.Token);
}
catch (OperationCanceledException ex)
{
status.Report(ex.Message);
}
-
\$\begingroup\$ You can take advantage of the
WithCancellation
method. \$\endgroup\$Peter Csala– Peter Csala2020年09月03日 07:36:14 +00:00Commented Sep 3, 2020 at 7:36 -
\$\begingroup\$ @PeterCsala
this.Server.GetLogMessages().WithCancellation(token)
? Sorry, I'm not familiar with it. \$\endgroup\$aepot– aepot2020年09月03日 07:55:40 +00:00Commented Sep 3, 2020 at 7:55 -
1\$\begingroup\$ Yes, as long as the
GetLogMessages
returns with anIAsyncEnumerable<T>
then you can use theWithCancellation
extension method to pass the ct to the async enumerator. For further details please check the following SO answer. \$\endgroup\$Peter Csala– Peter Csala2020年09月03日 08:46:43 +00:00Commented Sep 3, 2020 at 8:46 -
\$\begingroup\$ @PeterCsala got it, thank you! Updated the answer. \$\endgroup\$aepot– aepot2020年09月03日 08:48:20 +00:00Commented Sep 3, 2020 at 8:48
-
1\$\begingroup\$ In this case there is no need to explicitly call this:
token.ThrowIfCancellationRequested();
\$\endgroup\$Peter Csala– Peter Csala2020年09月03日 08:55:24 +00:00Commented Sep 3, 2020 at 8:55