Improved cross threaded invocation.
This commit is contained in:
parent
766d427b19
commit
a44c46333f
@ -4,15 +4,13 @@ using System.Threading;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
public abstract class AsyncNotifyPropertyChanged : INotifyPropertyChanged
|
||||
public abstract class AsyncNotifyPropertyChanged : SynchronizeInvoker, INotifyPropertyChanged
|
||||
{
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
private CrossThreadSync<PropertyChangedEventArgs> ThreadSync { get; } = new CrossThreadSync<PropertyChangedEventArgs>();
|
||||
|
||||
public AsyncNotifyPropertyChanged()
|
||||
=>ThreadSync.ObjectReceived += (_, args) => PropertyChanged?.Invoke(this, args);
|
||||
public AsyncNotifyPropertyChanged() { }
|
||||
|
||||
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
|
||||
=> ThreadSync.Post(new PropertyChangedEventArgs(propertyName));
|
||||
=>BeginInvoke(PropertyChanged, new object[] { this, new PropertyChangedEventArgs(propertyName) });
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,15 +11,14 @@ namespace LibationWinForms.BookLiberation.BaseForms
|
||||
{
|
||||
protected IStreamable Streamable { get; private set; }
|
||||
protected LogMe LogMe { get; private set; }
|
||||
private CrossThreadSync<Action> FormSync { get; } = new CrossThreadSync<Action>();
|
||||
private SynchronizeInvoker Invoker { get; init; }
|
||||
|
||||
public LiberationBaseForm()
|
||||
{
|
||||
//SynchronizationContext.Current will be null until the process contains a Form.
|
||||
//If this is the first form created, it will not exist until after execution
|
||||
//reaches inside the constructor. So need to reset the context here.
|
||||
FormSync.ResetContext();
|
||||
FormSync.ObjectReceived += (_, action) => action();
|
||||
Invoker = new SynchronizeInvoker();
|
||||
}
|
||||
|
||||
public void RegisterFileLiberator(IStreamable streamable, LogMe logMe = null)
|
||||
@ -134,7 +133,7 @@ namespace LibationWinForms.BookLiberation.BaseForms
|
||||
/// could cause it to freeze. Form.BeginInvoke won't work until the form is created
|
||||
/// (ie. shown) because Control doesn't get a window handle until it is Shown.
|
||||
/// </summary>
|
||||
private void OnStreamingBeginShow(object sender, string beginString) => FormSync.Send(Show);
|
||||
private void OnStreamingBeginShow(object sender, string beginString) => Invoker.Invoke(Show);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@ -1,45 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Threading;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
internal class CrossThreadSync<T>
|
||||
{
|
||||
public event EventHandler<T> ObjectReceived;
|
||||
private int InstanceThreadId { get; set; } = Thread.CurrentThread.ManagedThreadId;
|
||||
private SynchronizationContext SyncContext { get; set; } = SynchronizationContext.Current;
|
||||
private bool InvokeRequired => Thread.CurrentThread.ManagedThreadId != InstanceThreadId;
|
||||
|
||||
public void ResetContext()
|
||||
{
|
||||
SyncContext = SynchronizationContext.Current;
|
||||
InstanceThreadId = Thread.CurrentThread.ManagedThreadId;
|
||||
}
|
||||
|
||||
public void Send(T obj)
|
||||
{
|
||||
if (InvokeRequired)
|
||||
SyncContext.Send(SendOrPostCallback, new AsyncCompletedEventArgs(null, false, obj));
|
||||
else
|
||||
ObjectReceived?.Invoke(this, obj);
|
||||
}
|
||||
|
||||
public void Post(T obj)
|
||||
{
|
||||
if (InvokeRequired)
|
||||
SyncContext.Post(SendOrPostCallback, new AsyncCompletedEventArgs(null, false, obj));
|
||||
else
|
||||
ObjectReceived?.Invoke(this, obj);
|
||||
}
|
||||
|
||||
private void SendOrPostCallback(object asyncArgs)
|
||||
{
|
||||
var e = asyncArgs as AsyncCompletedEventArgs;
|
||||
|
||||
var userObject = (T)e.UserState;
|
||||
|
||||
ObjectReceived?.Invoke(this, userObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -96,7 +96,6 @@ namespace LibationWinForms
|
||||
public string Description { get; }
|
||||
public string DisplayTags => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated);
|
||||
public (LiberatedState, PdfState) Liberate => (LibraryCommands.Liberated_Status(Book), LibraryCommands.Pdf_Status(Book));
|
||||
|
||||
#endregion
|
||||
|
||||
#region Data Sorting
|
||||
|
||||
143
LibationWinForms/SynchronizeInvoker.cs
Normal file
143
LibationWinForms/SynchronizeInvoker.cs
Normal file
@ -0,0 +1,143 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Threading;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
public class SynchronizeInvoker : ISynchronizeInvoke
|
||||
{
|
||||
public bool InvokeRequired => Thread.CurrentThread.ManagedThreadId != InstanceThreadId;
|
||||
private int InstanceThreadId { get; set; } = Thread.CurrentThread.ManagedThreadId;
|
||||
private SynchronizationContext SyncContext { get; init; } = SynchronizationContext.Current;
|
||||
|
||||
public SynchronizeInvoker()
|
||||
{
|
||||
SyncContext = SynchronizationContext.Current;
|
||||
if (SyncContext is null)
|
||||
throw new NullReferenceException($"Could not capture a current {nameof(SynchronizationContext)}");
|
||||
}
|
||||
|
||||
public IAsyncResult BeginInvoke(Action action) => BeginInvoke(action, null);
|
||||
public IAsyncResult BeginInvoke(Delegate method) => BeginInvoke(method, null);
|
||||
public IAsyncResult BeginInvoke(Delegate method, object[] args)
|
||||
{
|
||||
var tme = new ThreadMethodEntry(method, args);
|
||||
|
||||
if (InvokeRequired)
|
||||
{
|
||||
SyncContext.Post(ThreadMethodEntry.OnSendOrPostCallback, tme);
|
||||
}
|
||||
else
|
||||
{
|
||||
tme.Complete();
|
||||
tme.CompletedSynchronously = true;
|
||||
}
|
||||
return tme;
|
||||
}
|
||||
|
||||
public object EndInvoke(IAsyncResult result)
|
||||
{
|
||||
if (result is not ThreadMethodEntry crossThread)
|
||||
throw new ArgumentException($"{nameof(result)} was not returned by {nameof(BeginInvoke)}");
|
||||
|
||||
if (!crossThread.IsCompleted)
|
||||
crossThread.AsyncWaitHandle.WaitOne();
|
||||
|
||||
return crossThread.ReturnValue;
|
||||
}
|
||||
|
||||
public object Invoke(Action action) => Invoke(action, null);
|
||||
public object Invoke(Delegate method) => Invoke(method, null);
|
||||
public object Invoke(Delegate method, object[] args)
|
||||
{
|
||||
var tme = new ThreadMethodEntry(method, args);
|
||||
|
||||
if (InvokeRequired)
|
||||
{
|
||||
SyncContext.Send(ThreadMethodEntry.OnSendOrPostCallback, tme);
|
||||
}
|
||||
else
|
||||
{
|
||||
tme.Complete();
|
||||
tme.CompletedSynchronously = true;
|
||||
}
|
||||
|
||||
return tme.ReturnValue;
|
||||
}
|
||||
|
||||
private class ThreadMethodEntry : IAsyncResult
|
||||
{
|
||||
public object AsyncState => null;
|
||||
public bool CompletedSynchronously { get; internal set; }
|
||||
public bool IsCompleted { get; private set; }
|
||||
public object ReturnValue { get; private set; }
|
||||
public WaitHandle AsyncWaitHandle
|
||||
{
|
||||
get
|
||||
{
|
||||
if (resetEvent == null)
|
||||
{
|
||||
lock (invokeSyncObject)
|
||||
{
|
||||
if (resetEvent == null)
|
||||
{
|
||||
resetEvent = new ManualResetEvent(initialState: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
return resetEvent;
|
||||
}
|
||||
}
|
||||
|
||||
private object invokeSyncObject = new object();
|
||||
private Delegate method;
|
||||
private object[] args;
|
||||
private ManualResetEvent resetEvent;
|
||||
|
||||
public ThreadMethodEntry(Delegate method, object[] args)
|
||||
{
|
||||
this.method = method;
|
||||
this.args = args;
|
||||
resetEvent = new ManualResetEvent(initialState: false);
|
||||
}
|
||||
/// <summary>
|
||||
/// This callback executes on the SynchronizationContext thread.
|
||||
/// </summary>
|
||||
public static void OnSendOrPostCallback(object asyncArgs)
|
||||
{
|
||||
var e = asyncArgs as ThreadMethodEntry;
|
||||
|
||||
e.Complete();
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (method)
|
||||
{
|
||||
case Action actiton:
|
||||
actiton();
|
||||
break;
|
||||
default:
|
||||
ReturnValue = method.DynamicInvoke(args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsCompleted = true;
|
||||
resetEvent.Set();
|
||||
}
|
||||
}
|
||||
|
||||
~ThreadMethodEntry()
|
||||
{
|
||||
if (resetEvent != null)
|
||||
{
|
||||
resetEvent.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user