pingu2k4
pingu2k4

Reputation: 1050

How do I not freeze the UI thread?

I can't figure this out... I have followed several different instructios I have found everywhere, but I can't find out how to not freeze the UI thread whilst performing a few tasks.

I run this when the window loads. In root of window in MainWindow.xaml I have:

<i:Interaction.Triggers>
    <i:EventTrigger EventName="ContentRendered">
        <i:InvokeCommandAction Command="{Binding WindowLoaded}" />
    </i:EventTrigger>
</i:Interaction.Triggers>

That triggers the following:

private void WindowLoadedEx(object p)
{
    DBMan();
    LoadValues();
    //Boot = false;
    //MainMenuVisibility = true;
}

DBMan() is negligable, the problem isn't here - it is in LoadValues(). I have chopped and changed this a lot trying out various different things, nothing working.

private void LoadValues()
{
    //ThreadStart AddHeroesRef = new ThreadStart(HeroesDBAddHeroes);
    //Thread AddHeroesThread = new Thread(AddHeroesRef);
    //AddHeroesThread.Start();
    ////HeroesDBAddHeroes();
    //ThreadStart AddCommentsRef = new ThreadStart(HeroesDBAddComments);
    //Thread AddCommentsThread = new Thread(AddCommentsRef);
    //AddCommentsThread.Start();
    ////HeroesDBAddComments();
    ThreadStart LoadValuesRef = new ThreadStart(LoadValuesThreaded);
    Thread LoadValuesThread = new Thread(LoadValuesRef);
    LoadValuesThread.Start();
    //HeroesDBAssignComments();
    //SetDBIDs();
}

private async void LoadValuesThreaded()
{
    ThreadStart AddHeroesRef = new ThreadStart(HeroesDBAddHeroes);
    Thread AddHeroesThread = new Thread(AddHeroesRef);
    AddHeroesThread.Start();
    //HeroesDBAddHeroes();
    ThreadStart AddCommentsRef = new ThreadStart(HeroesDBAddComments);
    Thread AddCommentsThread = new Thread(AddCommentsRef);
    AddCommentsThread.Start();
    //HeroesDBAddComments();

    while (AddHeroesThread.IsAlive || AddCommentsThread.IsAlive)
    {
        Thread.Sleep(1);
    }
    HeroesDBAssignComments();
    SetDBIDs();

    Boot = false;
    MainMenuVisibility = true;
}

(As you can see, ive tried changing different things and cant get anything to work).

Basically, both HeroesDBAddHeroes() and HeroesDBAddComments() need to be run together, they have no conflicts with each other etc. I will in future have other methods to run here also, at the same time in seperate threads.

However, I need to wait for these to all be complete before continuing and allowing HeroesDBAssignComments() to run. If everything above isnt complete already, it wont work. Then, I need to change the booleans Boot and MainMenuVisibility once HeroesDBAssignComments is complete.

Upvotes: 0

Views: 2466

Answers (3)

Lukazoid
Lukazoid

Reputation: 19416

Using the Task-based Asynchronous Pattern (TAP) makes problems like this very simple.

Your top level method WindowLoadedEx can be declared as async void, as it is basically a glorified event-handler. Then you may use async/await with the captured WPF synchronization context to publish UI changes on the correct thread, i.e:

private async void WindowLoadedEx(object p)
{
    DBMan();
    await LoadValuesAsync();
    Boot = false;
    MainMenuVisibility = true;
}

private async Task LoadValuesAsync()
{
    var addHeroesTask = HeroesDBAddHeroesAsync();
    var addCommentsTask = HeroesDBAddCommentsAsync();

    // We cannot assign comments or IDs until these have been added
    await Task.WhenAll(addHeroesTask, addCommentsTask).ConfigureAwait(false);

    // TODO Should these both also be asynchronous?
    HeroesDBAssignComments();
    SetDBIDs();
}

Notice I have used HeroesDBAddHeroesAsync and HeroesDBAddCommentsAsync as I believe these are truly asynchronous database operations.

Read up about the use of async/await here.

Upvotes: 1

Dbl
Dbl

Reputation: 5893

Since you didn't specify a .net version i'll just suggest a 4.0 way of dealing with this:

void BlockingMethod() {
    Task.Factory.StartNew(() => {
        // blocking work here, db loading for example
    })
    .ContinueWith(result => {
        // update control here with the result of result.Result
       },
       TaskScheduler.FromCurrentSynchronizationContext()
    });
}

You might have to fix up the syntax a little.

What does this do?

Basically when BlockingMethod gets called it will start an asynchronous task. This method will be called by the UI in your case, which is why your UI is freezing.

Because we're asynchronous the code we've passed for execution here will be running still, when the method is already left, since its' work is being dealt with by the framework.

Once the heavy loading is done the code in "ContinueWith" will be executed. We're passing TaskScheduler.FromCurrentSynchronizationContext(), because we need to be sure that this code is only executed in the context the method was originally called from (The ui sync context), otherwise you'll get an exception because wpf does not allow this.

Upvotes: 1

Christo S. Christov
Christo S. Christov

Reputation: 2309

You shouldn't use Thread.Sleep()to wait. Use a WaitHandle instead.

The basic way to use a WaitHandle is to invoke WaitOne() on the place where you would ilke to wait and then perform work on another thread and then invoke Set() on the waithandle.

I usually use EventWaitHandle

Upvotes: 0

Related Questions