NetSparkleUpdater / NetSparkle

NetSparkle is a C#, cross-platform, highly-configurable software update framework with pre-built UI for .NET developers compatible with .NET 4.6.2/.NET 6+, WinForms, WPF, and Avalonia; uses Ed25519 signatures. View basic usage here in the README and try the samples for yourself.
https://netsparkleupdater.github.io/NetSparkle/
MIT License
606 stars 84 forks source link

UI threading issues #470

Closed Deadpikle closed 3 weeks ago

Deadpikle commented 1 year ago

The ShowsUIOnMainThread thing is a mess. Originally intended in the 0.x/1.x series (which were WinForms only) to allow the forms to be shown on a separate thread and handled separately, the code has gotten more and more gross over the years such that it is confusing to understand in the first place and has associated bugs. I don't know why more people haven't complained.

The idea of this is "nice" on WinForms (why should this lib block all other windows) but breaks down due to the above problems.

Need to figure out some solutions to this. If this grows too much, then maybe rewriting this to be sane is a great reason to start the journey to a 3.x with major fixes for all this. (Which I personally do not have time for and may not for many months.)

If anyone reads this who has time, I would appreciate help. Especially if you can find ways to do this without breaking changes.

Deadpikle commented 1 year ago

Been thinking about this a lot the last few days. This is really caused by ShowsUIOnMainThread at the end of the day. Although this is likely fixable and could be attributed to code-bloat and poor programming over the years, mostly by yours truly, I have been reconsidering whether this option is worth the effort long-term.

By sheer number of downloads of 2.x, we are looking at around 41k-ish downloads of WinForms-based NetSparkleUpdater and 65k-ish downloads of WPF/Avalonia. (WPF alone is almost the same 41k and is 38k.) Considering that a majority of users do not want ShowsUIOnMainThread in the general case based on the issues some users are experiencing, at the very least the UIFactory should be asked for a default value, which should default to true for WPF and Avalonia.

I am seriously considering ripping out the ShowsUIOnMainThread option entirely and adding a sample for WinForms on how to do this from the user end of things so that those who want to avoid making WinForms forms model still have an easy way to set that up.

Benefits of this include:

Cons include:

This would be considered a breaking change and thus start the 3.0 breaking changes process, which may need to be much smaller than originally envisioned unless some others help step up and join in the dev since my time has been more limited recently (not being mean, just realistic :smile:).

I could force a default to true on WPF and Avalonia and call it a bug-fix, but that seems disingenuous at best.

Sometimes developers just make mistakes, and hindsight at what should have happened is best. We all learn over time.

I welcome input from anyone on this.


Edit (as I don't want to alert watchers):

For 3.x:

See also if needed:

kenjiuno commented 8 months ago

I agree that ShowsUIOnMainThread needs to be refined.

Probably it will help if developers can bring their own dispatcher like this.

  public Action<Action> Dispatcher { get; set; }

The problem is that developer cannot control the lifetime of background thread which NetSparkle will launch implicitly.

Sometimes it needs to use Form.ShowDialog() in order to use message loop inside ShowDialog about WinForms app.

public void ShowCannotDownloadAppcast(SparkleUpdater sparkle, string appcastUrl)
{
    _newUpdateErrorForm().Using(it => it.ShowDialog());
}

Just to make sure Using is like this:

internal static class UsingExtension
{
    internal static R Using<T, R>(this T it, Func<T, R> func) where T : IDisposable
    {
        using (it)
        {
            return func(it);
        }
    }
}

If developers can bring their own control method, the problem can be mitigated.

WinForms version of Dispatcher example:

_sparkle = new SparkleUpdater(
    provideHostDataRepository.AppcastUrl.Value,
    new DSAChecker(SecurityMode.Unsafe)
)
{
    Dispatcher = action =>
    {
        synchronizationContext.Send(
            () =>
            {
                action();
            },
            null
        );
    },
    UIFactory = newUI(),
    ShowsUIOnMainThread = false,
...

WPF version of Dispatcher example:

_sparkle = new SparkleUpdater(
    provideHostDataRepository.AppcastUrl.Value,
    new DSAChecker(SecurityMode.Unsafe)
)
{
    Dispatcher = action =>
    {
        Dispatcher.Invoke(
            () =>
            {
                action();
            }
        );
    },
    UIFactory = newUI(),
    ShowsUIOnMainThread = false,
...

I think this expansion will be easier to implement without breaking current workable implementation.

if (ShowsUIOnMainThread)
{
    _syncContext.Post(delegate
    {
        UpdateAvailableWindow.Close();
        UpdateAvailableWindow = null;
    }, null);
}
else if (Dispatcher != null)
{
    Dispatcher(
        () =>
        {
            UpdateAvailableWindow.Close();
            UpdateAvailableWindow = null;
        }
    );
}
else
{
    UpdateAvailableWindow.Close();
    UpdateAvailableWindow = null;
}