mirror of
https://github.com/aphrody-labs/NuGet.Client.git
synced 2026-10-15 05:49:05 +00:00
160 lines
4.6 KiB
C#
160 lines
4.6 KiB
C#
// Copyright (c) .NET Foundation. All rights reserved.
|
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
|
|
#nullable disable
|
|
|
|
using System;
|
|
using System.Reflection;
|
|
using System.Security.Permissions;
|
|
using System.Threading;
|
|
using System.Windows.Threading;
|
|
|
|
namespace Test.Utility.Threading
|
|
{
|
|
internal sealed class DispatcherThread : IDisposable
|
|
{
|
|
private readonly Thread _thread;
|
|
private Dispatcher _dispatcher;
|
|
private readonly object _invokeSyncRoot = new object();
|
|
private SynchronizationContext _syncContext;
|
|
private Exception _invokeException;
|
|
private bool _isInvoking;
|
|
private int _isClosed;
|
|
|
|
public DispatcherThread()
|
|
{
|
|
using (var resetEvent = new AutoResetEvent(initialState: false))
|
|
{
|
|
_thread = new Thread(() =>
|
|
{
|
|
// This is necessary to make sure a dispatcher exists for this thread.
|
|
var unused = Dispatcher.CurrentDispatcher;
|
|
|
|
unused.UnhandledException += new DispatcherUnhandledExceptionEventHandler(OnUnhandledException);
|
|
|
|
resetEvent.Set();
|
|
|
|
Dispatcher.Run();
|
|
})
|
|
{
|
|
Name = GetType().FullName,
|
|
IsBackground = true
|
|
};
|
|
_thread.SetApartmentState(ApartmentState.STA);
|
|
_thread.Start();
|
|
|
|
resetEvent.WaitOne();
|
|
|
|
AppDomain.CurrentDomain.DomainUnload += CurrentDomain_DomainUnload;
|
|
}
|
|
|
|
_dispatcher = Dispatcher.FromThread(_thread);
|
|
}
|
|
|
|
private void CurrentDomain_DomainUnload(object sender, EventArgs e)
|
|
{
|
|
// Need to dispose of the dispatch thread prior to the app domain going away.
|
|
Close();
|
|
}
|
|
|
|
public Thread Thread => _thread;
|
|
|
|
public SynchronizationContext SyncContext
|
|
{
|
|
get
|
|
{
|
|
if (_syncContext == null)
|
|
{
|
|
_syncContext = new DispatcherSynchronizationContext(_dispatcher);
|
|
}
|
|
return _syncContext;
|
|
}
|
|
}
|
|
|
|
private void OnUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
|
|
{
|
|
// We don't deal with unhandled exceptions from BeginInvoke,
|
|
// since there's no one to throw them to.
|
|
if (_isInvoking)
|
|
{
|
|
// e.Exception should be a TargetInvocationException from calling Invoke,
|
|
// the InnerExceptionis the one to forward on
|
|
if (e.Exception is TargetInvocationException)
|
|
{
|
|
_invokeException = e.Exception.InnerException;
|
|
}
|
|
else
|
|
{
|
|
_invokeException = e.Exception;
|
|
}
|
|
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
|
|
public void Invoke(Action action)
|
|
{
|
|
if (_isClosed != 0)
|
|
{
|
|
throw new ObjectDisposedException(nameof(DispatcherThread));
|
|
}
|
|
|
|
lock (_invokeSyncRoot)
|
|
{
|
|
_isInvoking = true;
|
|
_invokeException = null;
|
|
|
|
try
|
|
{
|
|
_dispatcher.Invoke(DispatcherPriority.Normal, action);
|
|
|
|
if (_invokeException != null)
|
|
{
|
|
throw _invokeException;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_isInvoking = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public DispatcherOperation BeginInvoke(Action action)
|
|
{
|
|
if (_isClosed != 0)
|
|
{
|
|
throw new ObjectDisposedException(nameof(DispatcherThread));
|
|
}
|
|
|
|
return _dispatcher.BeginInvoke(DispatcherPriority.Normal, action);
|
|
}
|
|
|
|
[SecurityPermission(SecurityAction.Demand, ControlThread = true)]
|
|
public void Close()
|
|
{
|
|
if (Interlocked.CompareExchange(ref _isClosed, 1, 0) == 0)
|
|
{
|
|
_dispatcher.InvokeShutdown();
|
|
_dispatcher = null;
|
|
|
|
try
|
|
{
|
|
if (!_thread.Join(TimeSpan.FromSeconds(20)))
|
|
{
|
|
_thread.Abort();
|
|
}
|
|
}
|
|
catch (ThreadAbortException)
|
|
{
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Close();
|
|
}
|
|
}
|
|
}
|