0

In my program I am using a separate thread to read from an external file and convey data to my program. I am in the process of trying to run a ProgressBar on the UI thread while that thread is running, in order to provide feedback to the user. As of now, I have a ProgressBar that opens, but does not close because I do not know how to tell it that the child thread has ended. I have read this tutorial on ProgressBars, and should make a note that I am not using a BackgroundWorker for my thread. Instead of this, I am just using a System.Threading.Thread. Please let me know if this is a problem.

The OpenFile() method that opens the Window containing the ProgressBar, and also launches the thread that will do the open file work:

public void OpenFile()
{
    //Open Progress Bar Window
    LoadingWindow = new LoadingScreen(App.MainWindowViewModel.LoadScreen); 
    LoadingWindow.Show();
    App.MainWindowViewModel.LoadScreen.IsIndeterminate = true;

    FILE_INPUT = true; //bool that Signifies that the background thread is running

    //Create Thread -- run routines in thread
    var openFileThread = new Thread(() => openDefault(txtFile)); //Passes the file
    openFileThread.Start(); //Start File open thread
}

//This thread reads in the file and transfers the data to the program
private void openDefault(StreamReader txtFile)
{
    //Gathers info from file and changes program accordingly

    FILE_INPUT = false; //background thread is no longer running
}

As of now, I do not have an increment method for my ProgressBar, as I would like to make sure that I can get it working correctly before working on a technical detail like that. Anyway, my question is... how and where in my program do I make it known that the background thread is done running and the LoadingWindow (ProgressBar) can be closed?

Some more code to help give you an idea of my program's flow:

Main Window Code Behind:

private void OpenFile_Click(object sender, RoutedEventArgs e)
{
    OpenFile(); //calls to the method that launches the new thread and the window with the Progress Bar

    //Wait until background Thread is complete
    while (ViewModel.FILE_INPUT == true) { }

    //After thread has stopped running, close the window (DOES NOT WORK)
    ViewModel.LoadingWindow.Close(); // -- Close Prog Bar
}

Eventual Solution:

//In OpenFile() method
LoadingWindow = new LoadingScreen(App.MainWindowViewModel.LoadScreen); //Open Progress Bar Window
LoadingWindow.Show();
App.MainWindowViewModel.LoadScreen.IsIndeterminate = true;

FILE_INPUT = true; //A file is being inputted*

Dispatcher UIDispatcher = Dispatcher.CurrentDispatcher;

Task.Factory.StartNew(() =>
{
     openDefault(txtFile); //Passes the file
     //After thread has stopped running, close the Loading Window
     UIDispatcher.Invoke((Action)(() => LoadingWindow.Close())); //Close progress bar
});
Eric after dark
  • 1,768
  • 4
  • 31
  • 79

1 Answers1

1

Without a complete code example, it's hard to provide a complete answer. Depending on how you're actually doing the file I/O and managing the ProgressBar, there might be other changes you could make to also improve the code. But to solve the immediate problem, I recommend taking advantage of the async/await pattern:

public async Task OpenFile()
{
    //Open Progress Bar Window
    LoadingWindow = new LoadingScreen(App.MainWindowViewModel.LoadScreen); 
    LoadingWindow.Show();
    App.MainWindowViewModel.LoadScreen.IsIndeterminate = true;

    FILE_INPUT = true; //bool that Signifies that the background thread is running

    //Create Thread -- run routines in thread
    await Task.Run(() => openDefault(txtFile)); //Passes the file
}

private async void OpenFile_Click(object sender, RoutedEventArgs e)
{
    await OpenFile(); //calls to the method that launches the new thread and the window with the Progress Bar

    //After thread has stopped running, close the window
    ViewModel.LoadingWindow.Close(); // -- Close Prog Bar
}

This will avoid causing your UI to freeze while the operation is working, while allowing the code to be written in a simple, straight-through manner.

It's possible that with the above changes, you can get rid of your FILE_INPUT variable altogether (it's not clear for what else you might be using it, but it might be nothing else).

Note that the OpenFile() method is changed to return Task. This is the norm for async methods without an actual return value. The same change would be made to the OpenFile_Click() method, except that as an event handler, these are generally required to return void. Either syntax is legal, but the former is preferred when possible so that access to the Task object is possible (for exception handling, completion, etc.).

EDIT: for those without .NET 4.5 and who cannot install the necessary components to support async/await (which is just a compile-time change), here's an example that will work in .NET 4.0:

public void OpenFile()
{
    //Open Progress Bar Window
    LoadingWindow = new LoadingScreen(App.MainWindowViewModel.LoadScreen); 
    LoadingWindow.Show();
    App.MainWindowViewModel.LoadScreen.IsIndeterminate = true;

    FILE_INPUT = true; //bool that Signifies that the background thread is running

    //Create Thread -- run routines in thread
    Task.Factory.StartNew(() =>
    {
        openDefault(txtFile); //Passes the file
        //After thread has stopped running, close the window
        Dispatcher.Invoke((Action)(() => ViewModel.LoadingWindow.Close())); // -- Close Prog Bar
    });
}

private void OpenFile_Click(object sender, RoutedEventArgs e)
{
    //calls to the method that launches the new thread and the window with the Progress Bar
    OpenFile();
}

(This is actually semantically a little different from the async/await implementation, in that rather than creating a continuation for the first task, I just put the window-close operation in that task. But it should have the same net result).

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • You can still use `async`/`await` in .NET 4.0 if you are willing to upgrade the relevant components: [Using async-await on .net 4](http://stackoverflow.com/questions/9110472/using-async-await-on-net-4). That said, you can accomplish a similar effect without it. I'll add an example to my answer. – Peter Duniho Nov 19 '14 at 18:15
  • I'm trying the code you put up in your edit section, and I'm getting an error for the `Dispatcher.Invoke(() => LoadingWindow.Close());` part that says: `Cannot convert lambda expression to type 'System.Delegate' because it is not a delegate type`. Are we not allowed to use a `Window` object in there? – Eric after dark Nov 19 '14 at 18:50
  • Ah, my mistake. I forgot that you also don't have the new overloads for `Invoke()` in .NET 4.0. I'll update the example. – Peter Duniho Nov 19 '14 at 18:56
  • Is `Action` just a generic that I need to fill in, because with the new example I get `An object reference is required for the non-static field....` – Eric after dark Nov 19 '14 at 19:08
  • Since the method isn't static, the error message is still puzzling. Maybe it's complaining about something different. This is why the question should include a complete code example. – Peter Duniho Nov 19 '14 at 19:16
  • This was just an error due to my lack of knowledge. I solved it by creating a `Dispatcher` object and using that one. I'll update my answer with the solution that I used. – Eric after dark Nov 19 '14 at 19:17