fix gachalog refresh crash

This commit is contained in:
DismissedLight
2023-08-19 18:16:42 +08:00
parent 282eb228d9
commit cb06949e60
8 changed files with 84 additions and 41 deletions

View File

@@ -0,0 +1,46 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Threading;
internal class DispatcherQueueProgress<T> : IProgress<T>
{
private readonly SynchronizationContext synchronizationContext;
private readonly Action<T>? handler;
private readonly SendOrPostCallback invokeHandlers;
public DispatcherQueueProgress(Action<T> handler, SynchronizationContext synchronizationContext)
{
this.synchronizationContext = synchronizationContext;
invokeHandlers = new SendOrPostCallback(InvokeHandlers);
ArgumentNullException.ThrowIfNull(handler);
this.handler = handler;
}
public event EventHandler<T>? ProgressChanged;
[SuppressMessage("", "VSTHRD001")]
public void Report(T value)
{
Action<T>? handler = this.handler;
EventHandler<T>? changedEvent = ProgressChanged;
if (handler is not null || changedEvent is not null)
{
synchronizationContext.Post(invokeHandlers, value);
}
}
[SuppressMessage("", "SH007")]
private void InvokeHandlers(object? state)
{
T value = (T)state!;
Action<T>? handler = this.handler;
EventHandler<T>? changedEvent = ProgressChanged;
handler?.Invoke(value);
changedEvent?.Invoke(this, value);
}
}

View File

@@ -11,7 +11,7 @@ namespace Snap.Hutao.Core.Threading;
/// 等待此类型对象后上下文会被切换至主线程
/// </summary>
[HighQuality]
internal readonly struct DispatherQueueSwitchOperation : IAwaitable<DispatherQueueSwitchOperation>, ICriticalAwaiter
internal readonly struct DispatcherQueueSwitchOperation : IAwaitable<DispatcherQueueSwitchOperation>, ICriticalAwaiter
{
private readonly DispatcherQueue dispatherQueue;
@@ -19,7 +19,7 @@ internal readonly struct DispatherQueueSwitchOperation : IAwaitable<DispatherQue
/// 构造一个新的调度器队列等待器
/// </summary>
/// <param name="dispatherQueue">调度器队列</param>
public DispatherQueueSwitchOperation(DispatcherQueue dispatherQueue)
public DispatcherQueueSwitchOperation(DispatcherQueue dispatherQueue)
{
this.dispatherQueue = dispatherQueue;
}
@@ -31,7 +31,7 @@ internal readonly struct DispatherQueueSwitchOperation : IAwaitable<DispatherQue
}
/// <inheritdoc/>
public DispatherQueueSwitchOperation GetAwaiter()
public DispatcherQueueSwitchOperation GetAwaiter()
{
return this;
}

View File

@@ -8,6 +8,8 @@ namespace Snap.Hutao.Core.Threading;
/// </summary>
internal interface ITaskContext
{
IProgress<T> CreateProgressForMainThread<T>(Action<T> handler);
/// <summary>
/// 在主线程上同步等待执行操作
/// </summary>
@@ -26,5 +28,5 @@ internal interface ITaskContext
/// </summary>
/// <remarks>使用 <see cref="SwitchToBackgroundAsync"/> 异步切换到 后台线程</remarks>
/// <returns>等待体</returns>
DispatherQueueSwitchOperation SwitchToMainThreadAsync();
DispatcherQueueSwitchOperation SwitchToMainThreadAsync();
}

View File

@@ -11,6 +11,7 @@ namespace Snap.Hutao.Core.Threading;
[Injection(InjectAs.Singleton, typeof(ITaskContext))]
internal sealed class TaskContext : ITaskContext
{
private readonly DispatcherQueueSynchronizationContext dispatcherQueueSynchronizationContext;
private readonly DispatcherQueue dispatcherQueue;
/// <summary>
@@ -19,8 +20,8 @@ internal sealed class TaskContext : ITaskContext
public TaskContext()
{
dispatcherQueue = DispatcherQueue.GetForCurrentThread();
DispatcherQueueSynchronizationContext context = new(dispatcherQueue);
SynchronizationContext.SetSynchronizationContext(context);
dispatcherQueueSynchronizationContext = new(dispatcherQueue);
SynchronizationContext.SetSynchronizationContext(dispatcherQueueSynchronizationContext);
}
/// <inheritdoc/>
@@ -30,7 +31,7 @@ internal sealed class TaskContext : ITaskContext
}
/// <inheritdoc/>
public DispatherQueueSwitchOperation SwitchToMainThreadAsync()
public DispatcherQueueSwitchOperation SwitchToMainThreadAsync()
{
return new(dispatcherQueue);
}
@@ -47,4 +48,9 @@ internal sealed class TaskContext : ITaskContext
dispatcherQueue.Invoke(action);
}
}
public IProgress<T> CreateProgressForMainThread<T>(Action<T> handler)
{
return new DispatcherQueueProgress<T>(handler, dispatcherQueueSynchronizationContext);
}
}

View File

@@ -34,4 +34,14 @@ internal sealed class GachaLogFetchStatus
/// 当前获取的物品
/// </summary>
public List<Item> Items { get; set; } = new(20);
public string Header
{
get
{
return AuthKeyTimeout
? SH.ViewDialogGachaLogRefreshProgressAuthkeyTimeout
: SH.ViewDialogGachaLogRefreshProgressDescription.Format(ConfigType.GetLocalizedDescription());
}
}
}

View File

@@ -7,27 +7,27 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shvc="using:Snap.Hutao.View.Control"
xmlns:shvh="using:Snap.Hutao.View.Helper"
Title="{shcm:ResourceString Name=ViewDialogGachaLogRefreshProgressTitle}"
Style="{StaticResource DefaultContentDialogStyle}"
mc:Ignorable="d">
<ContentDialog.Resources>
<DataTemplate x:Key="GachaItemDataTemplate">
<Grid Width="40" Height="40">
<shvc:ItemIcon
Badge="{Binding Badge}"
Icon="{Binding Icon}"
Quality="{Binding Quality}"/>
</Grid>
<shvc:ItemIcon
shvh:FrameworkElementHelper.SquareLength="60"
Badge="{Binding Badge}"
Icon="{Binding Icon}"
Quality="{Binding Quality}"/>
</DataTemplate>
</ContentDialog.Resources>
<StackPanel>
<TextBlock Text="{x:Bind Status.Header, Mode=OneWay}"/>
<cwc:HeaderedItemsControl
x:Name="GachaItemsPresenter"
Padding="0,8,0,0"
HorizontalAlignment="Left"
ItemTemplate="{StaticResource GachaItemDataTemplate}">
ItemTemplate="{StaticResource GachaItemDataTemplate}"
ItemsSource="{x:Bind Status.Items, Mode=OneWay}">
<cwc:HeaderedItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<cwc:UniformGrid

View File

@@ -30,30 +30,9 @@ internal sealed partial class GachaLogRefreshProgressDialog : ContentDialog
/// <summary>
/// 接收进度更新
/// </summary>
/// <param name="state">状态</param>
public void OnReport(GachaLogFetchStatus state)
/// <param name="status">状态</param>
public void OnReport(GachaLogFetchStatus status)
{
Status = state;
// TODO: test new binding approach
GachaItemsPresenter.Header = state.AuthKeyTimeout
? SH.ViewDialogGachaLogRefreshProgressAuthkeyTimeout
: SH.ViewDialogGachaLogRefreshProgressDescription.Format(state.ConfigType.GetLocalizedDescription());
// Binding not working here.
GachaItemsPresenter.Items.Clear();
// System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
foreach (Item item in state.Items.ToList())
{
GachaItemsPresenter.Items.Add(new ItemIcon
{
Width = 60,
Height = 60,
Quality = item.Quality,
Icon = item.Icon,
Badge = item.Badge,
});
}
Status = status;
}
}

View File

@@ -160,7 +160,7 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
GachaLogRefreshProgressDialog dialog = await contentDialogFactory.CreateInstanceAsync<GachaLogRefreshProgressDialog>().ConfigureAwait(false);
ContentDialogHideToken hideToken = await dialog.BlockAsync(taskContext).ConfigureAwait(false);
Progress<GachaLogFetchStatus> progress = new(dialog.OnReport);
IProgress<GachaLogFetchStatus> progress = taskContext.CreateProgressForMainThread<GachaLogFetchStatus>(dialog.OnReport);
bool authkeyValid;
try