4
\$\begingroup\$

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.

asked Sep 1, 2020 at 18:27
\$\endgroup\$
8
  • 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\$ Commented Sep 1, 2020 at 23:52
  • \$\begingroup\$ Have you considered to use DataFlow for this problem? \$\endgroup\$ Commented Sep 3, 2020 at 10:24
  • 1
    \$\begingroup\$ @aepot It may be able to be fixed. That is a different issue though. \$\endgroup\$ Commented Sep 3, 2020 at 12:26
  • \$\begingroup\$ @PeterCsala I have not heard of that. What is it? \$\endgroup\$ Commented 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\$ Commented Sep 4, 2020 at 23:25

1 Answer 1

3
\$\begingroup\$

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);
}
answered Sep 2, 2020 at 0:15
\$\endgroup\$
5
  • \$\begingroup\$ You can take advantage of the WithCancellation method. \$\endgroup\$ Commented Sep 3, 2020 at 7:36
  • \$\begingroup\$ @PeterCsala this.Server.GetLogMessages().WithCancellation(token)? Sorry, I'm not familiar with it. \$\endgroup\$ Commented Sep 3, 2020 at 7:55
  • 1
    \$\begingroup\$ Yes, as long as the GetLogMessages returns with an IAsyncEnumerable<T> then you can use the WithCancellation extension method to pass the ct to the async enumerator. For further details please check the following SO answer. \$\endgroup\$ Commented Sep 3, 2020 at 8:46
  • \$\begingroup\$ @PeterCsala got it, thank you! Updated the answer. \$\endgroup\$ Commented Sep 3, 2020 at 8:48
  • 1
    \$\begingroup\$ In this case there is no need to explicitly call this:token.ThrowIfCancellationRequested(); \$\endgroup\$ Commented Sep 3, 2020 at 8:55

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.