From ed58d03fdc603a1a13e2446905d4da6370be49c8 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Wed, 16 Sep 2026 13:51:11 +0200 Subject: [PATCH] Record why the directory dialog reports from its worker thread --- ...ataSourceLocalDirectoryInfoDialog.razor.cs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryInfoDialog.razor.cs b/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryInfoDialog.razor.cs index 8d7431ea..458dbac4 100644 --- a/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryInfoDialog.razor.cs +++ b/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryInfoDialog.razor.cs @@ -60,6 +60,22 @@ public partial class DataSourceLocalDirectoryInfoDialog : MSGComponentBase private bool IsDirectoryAvailable => this.directoryInfo.Exists; + /// + /// Takes the next file which the directory scan found. + /// + /// + /// This runs on the scan's own thread, and it does so deliberately, although the scan asks its + /// callers to reach for a dispatcher. That request is about updating the UI, and none of these + /// callbacks does: they write fields and nothing else.

+ /// Why that holds is worth writing down, because the code does not show it. The string builder + /// has exactly one writer -- this method, on that one thread -- and nobody else ever reads it. + /// What the renderer reads is the text field beside it, and assigning a string reference is + /// atomic, so a render sees the whole previous text or the whole new one, never half of either. + /// Building that text anew costs little, because the scan stops reporting files once it has + /// reported a hundred. And a render happens only when the refresh timer ticks, which goes + /// through the dispatcher, so a reading taken a moment too early is replaced 1.6 seconds later + /// anyway. + ///
private void UpdateFileList(string file) { this.directoryFiles.Append("- "); @@ -67,13 +83,38 @@ public partial class DataSourceLocalDirectoryInfoDialog : MSGComponentBase this.directoryFilesText = this.directoryFiles.ToString(); } + /// + /// Takes the size which the directory scan has added up so far. + /// + /// + /// Two threads call this, but never at the same time: the scan reports its progress from its + /// own thread, and the final figure follows once that thread has finished. A long is written in + /// one piece on all six targets we ship, which are 64 bit throughout, and the renderer reads it + /// only when the refresh timer ticks. The remark on the file list carries the reasoning these + /// callbacks share. + /// private void UpdateDirectorySize(long size) { this.directorySizeBytes = size; } + /// + /// Takes the number of files which the directory scan has counted so far. + /// + /// + /// Reported from the same two threads as the size above, and safe for the same reason. + /// private void UpdateDirectoryFiles(long numFiles) => this.directorySizeNumFiles = numFiles; + /// + /// Takes the news that the directory scan has finished. + /// + /// + /// This one, unlike the three above, does not run on the scan's thread. The scan invokes it + /// after awaiting its worker, and that continuation returns to the dispatcher this dialog was + /// initialized on. Stopping the timer and asking for a render from here is therefore no + /// different from doing either in a lifecycle method. + /// private void DirectoryOperationDone() { this.refreshTimer.Stop();