-2

I am trying to get a list of files and iterate them within an asynchronous task so the UI thread is free to update an output window (or progress bar). This is simply to free up the UI thread. When I use a plain old string[] (var files = new[] {"test path"}); it works great.
I'll also note that I have an example of this code working with Directory.GetFiles() removed. However, any time I introduce Directory or DirecoryInfo, the UI thread is being blocked. The result is the same whether I retrieve the files inside or outside of the task.

Why does this code block the UI thread? I don't necessarily need to asynchronously iterate the files (these are the only type of answers I find when I search the topic). I just need to get a list of the files and iterate over them within an async context.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data.SQLite;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;

namespace Indexer
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private CancellationTokenSource _stopWorkingCts;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void StartButton_Click(object sender, RoutedEventArgs e)
        {
            if (_stopWorkingCts == null || _stopWorkingCts.IsCancellationRequested)
            {
                _stopWorkingCts = new CancellationTokenSource();

                AnalyzeFiles(this);
            }
        }

        private void AnalyzeFiles(MainWindow mainWindow)
        {
            UpdateWindow("Analyzing files.");

            Task.Run(() =>
            {
                var files = Directory.GetFiles(_path, "*.dat", SearchOption.TopDirectoryOnly);
                foreach (var dir in files)
                {
                    if (!_stopWorkingCts.IsCancellationRequested)
                    {
                        mainWindow.UpdateWindow($"test {dir}");
                    }
                    else
                    {
                        break;
                    }
                }
            });
        }

        private void StopButton_Click(object sender, RoutedEventArgs e)
        {
            UpdateWindow("Connection closed.", false);

            if (_stopWorkingCts != null)
            {
                _stopWorkingCts.Cancel();
                UpdateWindow($"Stop requested.", false);
            }
        }

        private void UpdateWindow(string text, bool queueMessage = false)
        {
            Dispatcher.BeginInvoke(new Action(() =>
            {
                if (queueMessage)
                {
                    newOutputLines.Add($"{DateTime.Now} - {text}");

                    if (newOutputLines.Count >= 5)
                    {
                        OutputBox.AppendText(string.Join(Environment.NewLine, newOutputLines.ToArray()) +
                                             Environment.NewLine);
                        OutputBox.ScrollToEnd();

                        newOutputLines.Clear();
                    }
                }
                else
                {
                    OutputBox.AppendText(text + Environment.NewLine);
                    OutputBox.ScrollToEnd();
                }

                if (OutputBox.LineCount >= 500)
                {
                    OutputBox.Text =
                        string.Join(Environment.NewLine,
                            OutputBox.Text.Split(new[] { Environment.NewLine }, StringSplitOptions.None)
                                .Skip(Math.Max(0, OutputBox.LineCount - 500)));
                }
            }));
        }
    }
}
alan
  • 6,705
  • 9
  • 40
  • 70
  • 1
    Please add the code of `UpdateWindow` – Camilo Terevinto Oct 22 '17 at 20:47
  • Also, it's not clear what "any time I introduce `Directory` or `DirectoryInfo`..." means. Where are you introducing it? – Camilo Terevinto Oct 22 '17 at 20:52
  • You're calling `mainWindow.UpdateWindow` from a non-UI thread. You can't do that. I assume this is WPF - you need to invoke that code on the dispatcher. – Enigmativity Oct 22 '17 at 21:23
  • Updated based on your helpful feedback. Full class code included now. As for the comment on the call to `main.UpdateWindow`, my understanding is that it is completely acceptable to make such a call in this way. Unless there is something specifically wrong with this code, I have a working example of multi-threaded calls to MainWindow in this fashion. Thanks for your feedback! – alan Oct 22 '17 at 21:30
  • Have a look at [UWP's StorageFolder class](https://learn.microsoft.com/en-us/uwp/api/windows.storage.storagefolder), as per [this answer](https://stackoverflow.com/a/37951556/7034621) – orhtej2 Oct 22 '17 at 21:31
  • 1
    @orhtej2 I appreciate the referral to UWP's previous answer, but this doesn't answer my question as posted. I'd rather understand why the thread is blocked. You may have provided a solution to the problem, but no explanation is provided. – alan Oct 22 '17 at 22:39
  • It is not true that _"Iterating Directory.GetFiles within Task is blocking UI thread"_. Which you'd know if you'd debugged the problem. If the UI thread hangs, it's because _something in the UI thread_ is blocking the UI thread. You simply have a long-running operation that you _failed to put into a worker thread_, i.e. the call to `GetFiles()`. See marked duplicate for additional details on the _correct_ way to move long-running operations to a thread other than the UI thread. – Peter Duniho Oct 23 '17 at 04:06
  • @PeterDuniho I disagree, my first attempt was to do exactly what you're suggesting and the result is the same. I'll update my question to include these details, but I would have hoped you might try your answer prior to marking my question down and flagging it as a duplicate. I'll also note that I spent several hours debugging this issue and scouring the internet for answers. Thank you for your input all the same. – alan Oct 23 '17 at 12:29
  • If you don't post the actual code you are having trouble with, you can't expect to get community input that would address the problem you are having. That said, there still is not a good [mcve] here that would reproduce the problem. The SO community is not in the habit of spending time trying to debug code when it's not possible to easily reproduce the issue. – Peter Duniho Oct 23 '17 at 16:33
  • 1
    _That_ said, if you are still seeing the UI lag, it's probably because for every file, you go back to the UI thread to update the UI. There are lots of questions on SO addressing this "saturated the UI with invokes" variation on the "blocked UI thread" problem; the solution is always the same: don't spend so much time in the UI thread. – Peter Duniho Oct 23 '17 at 16:33

1 Answers1

1

See Directory.GetFiles():

... when you use GetFiles, you must wait for the whole array of names to be returned before you can access the array.

So GetFiles() is a blocking call; simply move it inside your Task.

Change:

        var files = Directory.GetFiles(_path, "*.dat", SearchOption.TopDirectoryOnly);

        Task.Run(() =>
        {
            // ... rest of the code ...

To:

        Task.Run(() =>
        {
            var files = Directory.GetFiles(_path, "*.dat", SearchOption.TopDirectoryOnly);
            // ... rest of the code ...
Idle_Mind
  • 38,363
  • 3
  • 29
  • 40
  • 1
    Thank you for the suggestion. I apologize for not mentioning earlier that this was what I tried prior to posting this question. It seems when I make this call, the UI thread is still blocked. I know it doesn't seem like it should be, which is why I'm so confused as to what's happening. I have updated the code with your suggestion, but the problem remains. Thank you! – alan Oct 23 '17 at 12:34