WPF's Application.Current.Dispatcher. Why Current may be null

Disclaimer: This article is a blogger original article, follow the CC 4.0 BY-SA copyright agreement, reproduced, please attach the original source link and this statement.
This link: https://blog.csdn.net/WPwalter/article/details/102775164

In WPF program, there may be Application.Current.Dispatcher.Xxxsuch a code to let some logic back to the main UI thread. Because there have been discovered at the time of calling of this code NullReferenceException, so there are three small partner told me Currentand Dispatcherattributes as possible null.

However, in practice this is only possible Currentas nullwhile this context Dispatcheris definitely not to nullthe. (Of course, we discussed here are conventional programming means, if unconventional means, you can even let instance thisis nullit ......)


When your application exits, all the UI thread code is no longer executed, so it is safe; but all the non-UI thread code still continue, this time may be encountered at any time Application.Currentproperty is null.

Because the two parts are slightly longer described herein, it is split into two blog, it is easier to understand.

Application.Current Static properties

Source

Application Type of source code will be very long, so there is not posted, you can go here:

Among them, Currentthe return of _appInstancethe static field. So _appInstancethe field is nullthe timing that Application.Currentis nullthe timing.

/// <summary>
///     The Current property enables the developer to always get to the application in
///     AppDomain in which they are running.
/// </summary>
static public Application Current
{
    get
    {
        // There is no need to take the _globalLock because reading a
        // reference is an atomic operation. Moreover taking a lock
        // also causes risk of re-entrancy because it pumps messages.

        return _appInstance;
    }
}

Since the _appInstancefield is private, so only survey the class itself can find all assignment opportunities. (Reflection needs unconventional means excluded, because it means that the developer is more than funny - you can not blame yourself break the curse WPF framework)

The timing of the assignment

_appInstance The timing of the assignment, there are two:

  1. Application Instance constructor (note oh instance constructor is not static constructor);
  2. Application.DoShutdown method.

In ApplicationExamples constructor:

  • _appInstanceThe assignment is thread-safe, which means that multiple Applicationconfiguration example of the thread-safety issues will not lead to _appInstancea state field is wrong.
  • If _appCreatedInThisAppDomainis true, then, will throw an exception, this application domain organization create a second Applicationinstance of a type.
/// <summary>
///     Application constructor
/// </summary>
/// <SecurityNote>
///    Critical: This code posts a work item to start dispatcher if in the browser
///    PublicOk: It is ok because the call itself is not exposed and the application object does this internally.
/// </SecurityNote>
[SecurityCritical]
public Application()
{
    // 省略了一部分代码。
    lock(_globalLock)
    {
        // set the default statics
        // DO NOT move this from the begining of this constructor
        if (_appCreatedInThisAppDomain == false)
        {
            Debug.Assert(_appInstance == null, "_appInstance must be null here.");
            _appInstance = this;
            IsShuttingDown    = false;
            _appCreatedInThisAppDomain = true;
        }
        else
        {
            //lock will be released, so no worries about throwing an exception inside the lock
            throw new InvalidOperationException(SR.Get(SRID.MultiSingleton));
        }
    }
    // 省略了一部分代码。
}

In other words, this type is actually designed as a single example. After the first constructed example, the example of a single embodiment can be started.

Follow-up assignment

The only time to end this instance is the singleton Application.DoShutdownmethod. This is the only _appInstanceassigned to nullcode.

/// <summary>
/// DO NOT USE - internal method
/// </summary>
///<SecurityNote>
///     Critical: Calls critical code: Window.InternalClose
///     Critical: Calls critical code: HwndSource.Dispose
///     Critical: Calls critical code: PreloadedPackages.Clear()
///</SecurityNote>
[SecurityCritical]
internal virtual void DoShutdown()
{
    // 省略了一部分代码。

    // Event handler exception continuality: if exception occurs in ShuttingDown event handler,
    // our cleanup action is to finish Shuttingdown.  Since Shuttingdown cannot be cancelled.
    // We don't want user to use throw exception and catch it to cancel Shuttingdown.
    try
    {
        // fire Applicaiton Exit event
        OnExit(e);
    }
    finally
    {
        SetExitCode(e._exitCode);

        // By default statics are shared across appdomains, so need to clear
        lock (_globalLock)
        {
            _appInstance = null;
        }

        _mainWindow = null;
        _htProps = null;
        NonAppWindowsInternal = null;

        // 省略了一部分代码。
    }
}

This code can call the public API are:

  • Application.Shutdown Examples of methods
  • Cause Windowseveral methods closed InternalDispose( )
  • IBrowserHostServices.PostShutdown Interface Method

Therefore, all direct or indirect calls to the above methods will result in local Application.Currentproperty is assigned null.

Impact on the written code

From the above analysis that can, as long as you can in Application.DoShutdowncontinuing to execute code after execution, then this part of the code will be faced with Application.Currentas a nullrisk.

So, in the end what opportunity might encounter Application.Currentis nullit? This part of it is strongly associated with the business code readers used in the project.

But this part of the business code will have some common features to help you determine whether you might write encounter Application.Currentfor the nullcode.

This feature is: This code is Application.Currentnot in the same thread .

And Application.Currentnot in the same thread

For WPF program, most of your code may be generated by user interaction, even with the implementation of the follow-up code, it still is generated from the UI interaction. This code will not encounter Application.Currentfor the nullsituation.

However, if your code is triggered by non-UI thread, for example, in the Usbdevice changes, the other end of the communication, some asynchronous code callback, etc., the code is not Dispatcherwhether to schedule impact, almost certainly executed. Therefore, Application.Currenteven if the assignment is null, and they do not know, we will continue to perform, so they encounter Application.Currentis null.

It is a security thread on this nature.

Use Invoke/BeginInvoke/InvokeAsyncthe code will not go wrong

Application.DoShutdownThe method was ShutdownImplpacked, and all calls are entered from the packaging, so all could lead Application.Currentto nullcode that will call this method, that is, it calls the Dispatcher.CriticalInvokeShutdowninstance method.

/// <summary>
/// This method gets called on dispatch of the Shutdown DispatcherOperationCallback
/// </summary>
///<SecurityNote>
///  Critical: Calls critical code: DoShutdown, Dispatcher.CritcalInvokeShutdown()
///</SecurityNote>
[SecurityCritical]
private void ShutdownImpl()
{
    // Event handler exception continuality: if exception occurs in Exit event handler,
    // our cleanup action is to finish Shutdown since Exit cannot be cancelled. We don't
    // want user to use throw exception and catch it to cancel Shutdown.
    try
    {
        DoShutdown();
    }
    finally
    {
        // Quit the dispatcher if we ran our own.
        if (_ownDispatcherStarted == true)
        {
            Dispatcher.CriticalInvokeShutdown();
        }

        ServiceProvider = null;
    }
}

All of close Dispatchercalls there are two types, Applicationcalled when the close is an internal method CriticalInvokeShutdown.

  1. Immediately shut down CriticalInvokeShutdown, that is Sendthe priority Invokemethod of closure Sendpriority calls Invokealmost equivalent to a direct call (Why are equivalent instead of calling? Because also need to consider returning to Dispatcherthe thread where the initialization).
  2. Began to close BeginInvokeShutdown, that is the priority of the specified InvokeAsyncclosed method.

Closed Dispatchermeans that all use Invoke/BeginInvoke/InvokeAsynctasks will be terminated.

//<SecurityNote>
//  Critical - as it accesses security critical data ( window handle)
//</SecurityNote>
[SecurityCritical]
private void ShutdownImplInSecurityContext(Object state)
{
    // 省略了一部分代码。

    // Now that the queue is off-line, abort all pending operations,
    // including inactive ones.
    DispatcherOperation operation = null;
    do
    {
        lock(_instanceLock)
        {
            if(_queue.MaxPriority != DispatcherPriority.Invalid)
            {
                operation = _queue.Peek();
            }
            else
            {
                operation = null;
            }
        }

        if(operation != null)
        {
            operation.Abort();
        }
    } while(operation != null);

    // 省略了一部分代码。
}

Because of this termination code Dispatcherexecution thread is located, and all Invoke/BeginInvoke/InvokeAsynccode is also executed in this thread, so these tags are not concurrent. The code has been executed in this termination code before, but after this termination code will no longer perform any Invoke/BeginInvoke/InvokeAsynctask.

The simplest example code

The simplest example

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;

namespace Walterlv.Demo.ApplicationDispatcher
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            var app = new Application();
            Task.Delay(1000).ContinueWith(t =>
            {
                app.Dispatcher.InvokeAsync(() => app.Shutdown());
            });
            Task.Delay(2000).ContinueWith(t =>
            {
                Application.Current.Dispatcher.InvokeAsync(() => { });
            });
            app.Run();
            Thread.Sleep(2000);
        }
    }
}

in conclusion

All of the above summary analysis:

  1. Any Applicationnot in the same thread code that are likely to encounter Application.Currentis null.
  2. Any Applicationcode same thread, are likely to encounter Application.Currentis null.

This is actually a thread-safety issues. With all business developers can understand the argument description is:

When your application exits, all the UI thread code is no longer executed, so it is safe; but all the non-UI thread code still continue, this time may be encountered at any time Application.Currentproperty is null.

So, remember all the non-UI thread of code, if required transfer to the UI thread execution, remember the empty sentence:

private void OnUsbDeviceChanged(object sender, EventArgs e)
{
    // 记得这里需要判空,因为此上下文可能在非 UI 线程。
    Application.Current?.InvokeAsync(() => { });
}

Application.Dispatcher Instance Properties

About Application.Dispatcherthe possibility to nullanalyze due to longer, please see my other blog post:


Reference material


My blog will be starting in https://blog.walterlv.com/ , and will be released from CSDN which featured, but it is rarely updated once released.

If you see any do not understand the contents of the blog, please share. I built dotnet Vocational and Technical College welcome to join.

Creative Commons License

This work is Creative Commons Attribution - NonCommercial - ShareAlike 4.0 International License Agreement for licensing. Welcome to reprint, use, repost, but be sure to keep the article signed by Lu Yi (containing links: https://walterlv.blog.csdn.net/ ), shall not be used for commercial purposes, a work based on the article be sure to modify the same license release. If you have any questions, please contact me .

Guess you like

Origin blog.csdn.net/WPwalter/article/details/102775164