KennanChan / Revit.Async

Use task-based asynchronous pattern (TAP) to run Revit API code from any execution context.
MIT License
223 stars 51 forks source link

RevitTask.RunAsync freezes GUI #16

Closed efdiloreto closed 2 years ago

efdiloreto commented 2 years ago

When I use RevitTask.RunAsync (() => SomeMethod()) the GUI get frozen. If I use a simple task like Task.Delay(10000) it works correctly. What could be the issue?

Said that, thanks for this library. It's awesome!

KennanChan commented 2 years ago

Can you provide more detail about how you use it? Some code maybe?

efdiloreto commented 2 years ago

Yes of course!

I have this method:

    private async Task ProcessAsync(Action processor)
    {
        _errors.Clear();
        WeakReferenceMessenger.Default.Send(new StatusMessage(true)); // Here I send the a message to show a indeterminated progress bar to another ViewModel
        await RevitTask.RunAsync(processor);  // Here Task.Delay(10000) runs just fine
        WeakReferenceMessenger.Default.Send(new StatusMessage(false)); Here I send the a message to hide a indeterminated progress bar to another ViewModel
        if (_errors.Count > 0)
        {
            WeakReferenceMessenger.Default.Send(new ErrorsMessage(_errors));
        }
    }

the processor Action could be a method like this:

    public void DuplicateViews()
    {
        if (SelectedViews != null && DuplicateNumber > 0)
        {
            using (Transaction tr = new Transaction(_document))
            {
                tr.Start("Duplicate Views");
                foreach (View view in SelectedViews)
                {
                    for (int i = 1; i < DuplicateNumber + 1; i++)
                    {
                        Views.Duplicate(view, DuplicateOption);
                    }
                }
                tr.Commit();
            }
        }
    }
KennanChan commented 2 years ago

I don't see obvious errors in the code you pasted.

How about running some harmless code inside the "processor" instead, to see if it's the problem of the method DuplicateViews. Or you can wrap the DuplicateViews method with IExternalEventHandler manually, and raise it to see if it works.

Furthermore, If you don't mind, you can send me a demo project to reproduce this problem so I can do some debugging on it, you can find my email in the readme of this repo.

efdiloreto commented 2 years ago

How about running some harmless code inside the "processor" instead

Some non-Revit related code?

Yes, I can send you a demo project. Let me prepare one for you.

Thank you for your help!

KennanChan commented 2 years ago

What's the version of Revit you use? I need to prepare the environment

efdiloreto commented 2 years ago

I'm using a multiversion project. But you can try with 2021. I'll try to send you a test project byt tomorrow, it's ok?

KennanChan commented 2 years ago

Sure

KennanChan commented 2 years ago

I understand!

It is the word "async" that results in the misunderstanding.

One thing to clarify is that asynchronous doesn't mean multi-thread.

Although generally, when we call a method ending with "Async", we have the feeling that we are running this job in a multi-thread way. That is because multi-thread job needs to callback its job result to the main thread to present on the UI, at which time asynchronous takes its part.

Actually, asynchronous is a kind of strategy to schedule a future job in a former job to keep our application running forward, which is always used in a single-thread application model to ensure thread safety.

While multi-thread is a kind of technology to slice the CPU time into tiny parts to run multiple jobs "at the same time"(likely).

A Windows Form application is typically a single-thread application. if you want to update the UI, you should use the "Invoke" method to schedule your UI updating logic to the main thread to keep things right.

A WPF application is also a single-thread application. You also need to use Dispatcher to dispatch the UI updating logic to the main thread.

Another example is the browser who runs javascript under the same model.

A more general understanding is that, any application rendering a UI always adopts a single-thread model to ensure the UI is only updated on the main thread.

In the Revit world, it is the same thing. Revit API is used to update Revit model and Revit requires all the updating logic to run on the main thread. IExternalEvent is the mechanism to do the scheduling. We use it to schedule a Revit job to run Revit API on the main UI thread to keep Revit running in a thread-safe way.

Revit keeps this rule and Revit.Async cannot jump out of the circle to run Revit API in a background thread.

A correct view on Revit.Async is that it is a library to help developers to use the asynchronous pattern that Revit provides, in an easier and more friendly way. That's it. There is no multi-thread thing in this library.

In your case, duplicating a view is scheduled to run on the main thread, which is the same thread that runs the logic to update the progress bar. The more views to duplicate, the more frozen the UI feels. Revit.Async cannot optimize it.

KennanChan commented 2 years ago

I think I can close this issue now.