Improved cross thread execution and minor refactoring.
This commit is contained in:
parent
0e7930f2b6
commit
766d427b19
@ -7,31 +7,12 @@ namespace LibationWinForms
|
||||
public abstract class AsyncNotifyPropertyChanged : INotifyPropertyChanged
|
||||
{
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
private int InstanceThreadId { get; } = Thread.CurrentThread.ManagedThreadId;
|
||||
private bool InvokeRequired => Thread.CurrentThread.ManagedThreadId != InstanceThreadId;
|
||||
private SynchronizationContext SyncContext { get; } = SynchronizationContext.Current;
|
||||
private CrossThreadSync<PropertyChangedEventArgs> ThreadSync { get; } = new CrossThreadSync<PropertyChangedEventArgs>();
|
||||
|
||||
public AsyncNotifyPropertyChanged()
|
||||
=>ThreadSync.ObjectReceived += (_, args) => PropertyChanged?.Invoke(this, args);
|
||||
|
||||
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
|
||||
{
|
||||
var propertyChangedArgs = new PropertyChangedEventArgs(propertyName);
|
||||
|
||||
if (InvokeRequired)
|
||||
{
|
||||
SyncContext.Post(
|
||||
PostPropertyChangedCallback,
|
||||
new AsyncCompletedEventArgs(null, false, propertyChangedArgs));
|
||||
}
|
||||
else
|
||||
{
|
||||
OnPropertyChanged(propertyChangedArgs);
|
||||
}
|
||||
}
|
||||
private void PostPropertyChangedCallback(object asyncArgs)
|
||||
{
|
||||
var e = asyncArgs as AsyncCompletedEventArgs;
|
||||
|
||||
OnPropertyChanged(e.UserState as PropertyChangedEventArgs);
|
||||
}
|
||||
private void OnPropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);
|
||||
=> ThreadSync.Post(new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,8 +3,6 @@ using Dinah.Core.Net.Http;
|
||||
using Dinah.Core.Windows.Forms;
|
||||
using FileLiberator;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms.BookLiberation.BaseForms
|
||||
@ -13,15 +11,15 @@ namespace LibationWinForms.BookLiberation.BaseForms
|
||||
{
|
||||
protected IStreamable Streamable { get; private set; }
|
||||
protected LogMe LogMe { get; private set; }
|
||||
|
||||
private int InstanceThreadId { get; } = Thread.CurrentThread.ManagedThreadId;
|
||||
public new bool InvokeRequired => Thread.CurrentThread.ManagedThreadId != InstanceThreadId;
|
||||
private SynchronizationContext SyncContext { get; }
|
||||
private CrossThreadSync<Action> FormSync { get; } = new CrossThreadSync<Action>();
|
||||
|
||||
public LiberationBaseForm()
|
||||
{
|
||||
//Will be null if set outside constructor.
|
||||
SyncContext = SynchronizationContext.Current;
|
||||
//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();
|
||||
}
|
||||
|
||||
public void RegisterFileLiberator(IStreamable streamable, LogMe logMe = null)
|
||||
@ -130,30 +128,14 @@ namespace LibationWinForms.BookLiberation.BaseForms
|
||||
private void OnCompletedDispose(object sender, LibraryBook e) => this.UIThread(() => Dispose());
|
||||
|
||||
/// <summary>
|
||||
/// If StreamingBegin is fired from a worker thread, the window will be created on
|
||||
/// that UI thread. We need to make certain that we show the window on the same
|
||||
/// thread that created form, otherwise the form and the window handle will be on
|
||||
/// different threads, and the renderer will be on a worker thread which 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.
|
||||
/// If StreamingBegin is fired from a worker thread, the window will be created on that
|
||||
/// worker thread. We need to make certain that we show the window on the UI thread (same
|
||||
/// thread that created form), otherwise the renderer will be on a worker thread which
|
||||
/// 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)
|
||||
{
|
||||
static void sendCallback(object asyncArgs)
|
||||
{
|
||||
var e = asyncArgs as AsyncCompletedEventArgs;
|
||||
((Action)e.UserState)();
|
||||
}
|
||||
private void OnStreamingBeginShow(object sender, string beginString) => FormSync.Send(Show);
|
||||
|
||||
Action show = Show;
|
||||
|
||||
if (InvokeRequired)
|
||||
SyncContext.Send(
|
||||
sendCallback,
|
||||
new AsyncCompletedEventArgs(null, false, show));
|
||||
else
|
||||
show();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IStreamable event handlers
|
||||
|
||||
@ -52,7 +52,7 @@ namespace LibationWinForms.BookLiberation
|
||||
{
|
||||
public static async Task BackupSingleBookAsync(LibraryBook libraryBook, EventHandler<LibraryBook> completedAction = null)
|
||||
{
|
||||
Serilog.Log.Logger.Information("Begin backup single {@DebugInfo}", new { libraryBook?.Book?.AudibleProductId });
|
||||
Serilog.Log.Logger.Information($"Begin {nameof(BackupSingleBookAsync)} {{@DebugInfo}}", new { libraryBook?.Book?.AudibleProductId });
|
||||
|
||||
var logMe = LogMe.RegisterForm();
|
||||
var backupBook = CreateBackupBook(completedAction, logMe);
|
||||
@ -96,21 +96,6 @@ namespace LibationWinForms.BookLiberation
|
||||
await new BackupLoop(logMe, downloadPdf, automatedBackupsForm).RunBackupAsync();
|
||||
}
|
||||
|
||||
public static void DownloadFile(string url, string destination, bool showDownloadCompletedDialog = false)
|
||||
{
|
||||
Serilog.Log.Logger.Information($"Begin {nameof(DownloadFile)} for {url}");
|
||||
|
||||
void onDownloadFileStreamingCompleted(object o, string s)
|
||||
{
|
||||
if (showDownloadCompletedDialog)
|
||||
MessageBox.Show("File downloaded to:\r\n\r\n" + s);
|
||||
}
|
||||
|
||||
var downloadFile = CreateStreamable<DownloadFile, DownloadForm>(onDownloadFileStreamingCompleted);
|
||||
async void runDownload() => await downloadFile.PerformDownloadFileAsync(url, destination);
|
||||
new Task(runDownload).Start();
|
||||
}
|
||||
|
||||
private static IProcessable CreateBackupBook(EventHandler<LibraryBook> completedAction, LogMe logMe)
|
||||
{
|
||||
var downloadPdf = CreateProcessable<DownloadPdf, PdfDownloadForm>(logMe);
|
||||
@ -126,19 +111,40 @@ namespace LibationWinForms.BookLiberation
|
||||
return downloadDecryptBook;
|
||||
}
|
||||
|
||||
public static void DownloadFile(string url, string destination, bool showDownloadCompletedDialog = false)
|
||||
{
|
||||
Serilog.Log.Logger.Information($"Begin {nameof(DownloadFile)} for {url}");
|
||||
|
||||
void onDownloadFileStreamingCompleted(object sender, string savedFile)
|
||||
{
|
||||
Serilog.Log.Logger.Information($"Completed {nameof(DownloadFile)} for {url}. Saved to {savedFile}");
|
||||
|
||||
if (showDownloadCompletedDialog)
|
||||
MessageBox.Show($"File downloaded to:{Environment.NewLine}{Environment.NewLine}{savedFile}");
|
||||
}
|
||||
|
||||
var downloadFile = new DownloadFile();
|
||||
var downloadForm = new DownloadForm();
|
||||
downloadForm.RegisterFileLiberator(downloadFile);
|
||||
downloadFile.StreamingCompleted += onDownloadFileStreamingCompleted;
|
||||
|
||||
async void runDownload() => await downloadFile.PerformDownloadFileAsync(url, destination);
|
||||
new Task(runDownload).Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="IProcessable"/> and links it to a new <see cref="LiberationBaseForm"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TStrProc">The <see cref="IProcessable"/> derrived type to create.</typeparam>
|
||||
/// <typeparam name="TProcessable">The <see cref="IProcessable"/> derrived type to create.</typeparam>
|
||||
/// <typeparam name="TForm">The <see cref="LiberationBaseForm"/> derrived Form to create on <see cref="IProcessable.Begin"/>, Show on <see cref="IStreamable.StreamingBegin"/>, Close on <see cref="IStreamable.StreamingCompleted"/>, and Dispose on <see cref="IProcessable.Completed"/> </typeparam>
|
||||
/// <param name="logMe">The logger</param>
|
||||
/// <param name="completedAction">An additional event handler to handle <see cref="IProcessable.Completed"/></param>
|
||||
/// <returns>A new <see cref="IProcessable"/> of type <typeparamref name="TStrProc"/></returns>
|
||||
private static TStrProc CreateProcessable<TStrProc, TForm>(LogMe logMe, EventHandler<LibraryBook> completedAction = null)
|
||||
private static TProcessable CreateProcessable<TProcessable, TForm>(LogMe logMe, EventHandler<LibraryBook> completedAction = null)
|
||||
where TForm : LiberationBaseForm, new()
|
||||
where TStrProc : IProcessable, new()
|
||||
where TProcessable : IProcessable, new()
|
||||
{
|
||||
var strProc = new TStrProc();
|
||||
var strProc = new TProcessable();
|
||||
|
||||
strProc.Begin += (sender, libraryBook) =>
|
||||
{
|
||||
@ -147,33 +153,10 @@ namespace LibationWinForms.BookLiberation
|
||||
processForm.OnBegin(sender, libraryBook);
|
||||
};
|
||||
|
||||
if (completedAction != null)
|
||||
strProc.Completed += completedAction;
|
||||
strProc.Completed += completedAction;
|
||||
|
||||
return strProc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="IStreamable"/> and links it to a new <see cref="LiberationBaseForm"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="TStr">The <see cref="IStreamable"/> derrived type to create.</typeparam>
|
||||
/// <typeparam name="TForm">The <see cref="LiberationBaseForm"/> derrived Form to create, which will Show on <see cref="IStreamable.StreamingBegin"/> and Close, Dispose on <see cref="IStreamable.StreamingCompleted"/>.</typeparam>
|
||||
/// <param name="completedAction">An additional event handler to handle <see cref="IStreamable.StreamingCompleted"/></param>
|
||||
/// <returns>A new <see cref="IStreamable"/> of type <typeparamref name="TStr"/></returns>
|
||||
private static TStr CreateStreamable<TStr, TForm>(EventHandler<string> completedAction = null)
|
||||
where TForm : LiberationBaseForm, new()
|
||||
where TStr : IStreamable, new()
|
||||
{
|
||||
var streamable = new TStr();
|
||||
|
||||
var streamForm = new TForm();
|
||||
streamForm.RegisterFileLiberator(streamable);
|
||||
|
||||
if (completedAction != null)
|
||||
streamable.StreamingCompleted += completedAction;
|
||||
|
||||
return streamable;
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class BackupRunner
|
||||
|
||||
45
LibationWinForms/CrossThreadSync[T].cs
Normal file
45
LibationWinForms/CrossThreadSync[T].cs
Normal file
@ -0,0 +1,45 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user