Skip to content

Commit 5de2b74

Browse files
Utilize Task and Channel instead of Thread and ConcurrentQueue respectively (#92)
Co-authored-by: C. Augusto Proiete <[email protected]>
1 parent 5e4fe9f commit 5de2b74

File tree

2 files changed

+49
-66
lines changed

2 files changed

+49
-66
lines changed

src/Serilog.Sinks.RichTextBox.Wpf/Serilog.Sinks.RichTextBox.Wpf.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272

7373
<ItemGroup Condition=" '$(TargetFramework)' == 'net462' ">
7474
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.2" PrivateAssets="All" />
75+
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
7576
<Reference Include="PresentationCore" />
7677
<Reference Include="PresentationFramework" />
7778
<Reference Include="System" />

src/Serilog.Sinks.RichTextBox.Wpf/Sinks/RichTextBox/RichTextBoxSink.cs

Lines changed: 48 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
using System.Diagnostics;
2222
using System.IO;
2323
using System.Text;
24-
using System.Threading;
24+
using System.Threading.Channels;
25+
using System.Threading.Tasks;
2526
using System.Windows.Threading;
2627
using Serilog.Core;
2728
using Serilog.Events;
@@ -43,8 +44,8 @@ internal sealed class RichTextBoxSink : ILogEventSink, IDisposable
4344
private const int _defaultWriteBufferCapacity = 256;
4445

4546
private const int _batchSize = 200;
46-
private Thread _consumerThread;
47-
private ConcurrentQueue<LogEvent> _messageQueue;
47+
private const int _minimumDelayForIncompleteBatch = 25;
48+
private Channel<LogEvent> _messageChannel;
4849

4950
public RichTextBoxSink(IRichTextBox richTextBox, ITextFormatter formatter, DispatcherPriority dispatcherPriority, object syncRoot)
5051
{
@@ -62,83 +63,64 @@ public RichTextBoxSink(IRichTextBox richTextBox, ITextFormatter formatter, Dispa
6263

6364
_renderAction = Render;
6465

65-
_messageQueue = new ConcurrentQueue<LogEvent>();
66+
_messageChannel = Channel.CreateUnbounded<LogEvent>();
6667

67-
_consumerThread = new Thread(new ThreadStart(ProcessMessages)) { IsBackground = true };
68-
_consumerThread.Start();
68+
Task.Run(ProcessMessages);
6969
}
7070

71-
private enum States
71+
private async Task ProcessMessages()
7272
{
73-
Init,
74-
Dequeue,
75-
Log,
76-
}
77-
78-
private void ProcessMessages()
79-
{
80-
StringBuilder sb = new();
81-
Stopwatch sw = Stopwatch.StartNew();
82-
States state = States.Init;
8373
int msgCounter = 0;
74+
const string initial = $"<Paragraph xmlns =\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" xml:space=\"preserve\">";
75+
76+
StringBuilder sb = new(initial);
77+
78+
async Task<string> ReadChannelAsync()
79+
{
80+
var logEvent = await _messageChannel.Reader.ReadAsync();
81+
StringWriter writer = new();
82+
_formatter.Format(logEvent, writer);
83+
return writer.ToString();
84+
}
85+
86+
Task restartTimer() => Task.Delay(_minimumDelayForIncompleteBatch);
87+
88+
var incompleteBatchTask = restartTimer();
89+
var logEventTask = ReadChannelAsync();
8490

8591
while (true)
8692
{
87-
switch (state)
93+
var firstTask = await Task.WhenAny(incompleteBatchTask, logEventTask);
94+
95+
if (firstTask == logEventTask)
8896
{
89-
//prepare the string builder and data
90-
case States.Init:
91-
sb.Clear();
92-
sb.Append($"<Paragraph xmlns =\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" xml:space=\"preserve\">");
93-
msgCounter = 0;
94-
state = States.Dequeue;
95-
break;
96-
97-
case States.Dequeue:
98-
if (sw.Elapsed.TotalMilliseconds >= 25 || msgCounter >= _batchSize)
99-
{
100-
if (msgCounter == 0)
101-
{
102-
//no messages, retick
103-
sw.Restart();
104-
}
105-
else
106-
{
107-
//valid log condition
108-
state = States.Log;
109-
break;
110-
}
111-
}
112-
113-
if (_messageQueue.TryDequeue(out LogEvent logEvent) == false)
114-
{
115-
Thread.Sleep(1);
116-
continue;
117-
}
118-
119-
StringWriter writer = new();
120-
_formatter.Format(logEvent, writer);
121-
122-
//got a message from the queue, retick
123-
sw.Restart();
124-
125-
msgCounter++;
126-
sb.Append(writer.ToString());
127-
break;
128-
129-
case States.Log:
130-
sb.Append("</Paragraph>");
131-
string xamlParagraphText = sb.ToString();
132-
_richTextBox.BeginInvoke(_dispatcherPriority, _renderAction, xamlParagraphText);
133-
state = States.Init;
134-
break;
97+
sb.Append(await logEventTask);
98+
msgCounter++;
99+
if (msgCounter < _batchSize)
100+
{
101+
logEventTask = ReadChannelAsync();
102+
continue;
103+
}
135104
}
105+
else if (msgCounter == 0)
106+
{
107+
//no messages, restart timer
108+
incompleteBatchTask = restartTimer();
109+
continue;
110+
}
111+
112+
sb.Append("</Paragraph>");
113+
string xamlParagraphText = sb.ToString();
114+
await _richTextBox.BeginInvoke(_dispatcherPriority, _renderAction, xamlParagraphText);
115+
sb.Clear();
116+
sb.Append(initial);
117+
msgCounter = 0;
136118
}
137119
}
138120

139121
public void Emit(LogEvent logEvent)
140-
{
141-
_messageQueue.Enqueue(logEvent);
122+
{
123+
_messageChannel.Writer.TryWrite(logEvent);
142124
}
143125

144126
private void Render(string xamlParagraphText)

0 commit comments

Comments
 (0)