fix: deadlock in UI thread

This commit is contained in:
dantmnf
2023-04-01 15:59:03 +08:00
parent 4c4f8f4d6d
commit ab65e6ebf2
5 changed files with 105 additions and 73 deletions

View File

@@ -32,6 +32,7 @@ using StyletIoC;
namespace MaaWpfGui.Services
{
/// <summary>
/// Stage manager
/// </summary>
@@ -40,6 +41,9 @@ namespace MaaWpfGui.Services
[DllImport("MaaCore.dll")]
private static extern IntPtr AsstGetVersion();
private const string StageApi = "gui/StageActivity.json";
private const string TasksApi = "resource/tasks.json";
// model references
private readonly TaskQueueViewModel _taskQueueViewModel;
private readonly IMaaApiService _maaApiService;
@@ -57,49 +61,38 @@ namespace MaaWpfGui.Services
{
_taskQueueViewModel = container.Get<TaskQueueViewModel>();
_maaApiService = container.Get<IMaaApiService>();
UpdateStage(false);
UpdateStageLocal();
Execute.OnUIThread(async () =>
Task.Run(async () =>
{
var task = Task.Run(() =>
{
UpdateStage(true);
});
await task;
await UpdateStageWeb();
if (_taskQueueViewModel != null)
{
_taskQueueViewModel.UpdateDatePrompt();
_taskQueueViewModel.UpdateStageList(true);
Execute.OnUIThread(() =>
{
_taskQueueViewModel.UpdateDatePrompt();
_taskQueueViewModel.UpdateStageList(true);
});
}
});
}
public void UpdateStage(bool fromWeb)
public void UpdateStageLocal()
{
if (fromWeb)
UpdateStageInternal(LoadLocalStages());
}
public async Task UpdateStageWeb()
{
if (await CheckWebUpdate())
{
// Check if we need to update from the web
string lastUpdateTimeFile = "lastUpdateTime.json";
JObject localLastUpdatedJson = _maaApiService.LoadApiCache(lastUpdateTimeFile);
JObject webLastUpdatedJson = _maaApiService.RequestMaaApiWithCache(lastUpdateTimeFile).ConfigureAwait(false).GetAwaiter().GetResult();
if (localLastUpdatedJson != null && webLastUpdatedJson != null)
{
long localTimestamp = localLastUpdatedJson["timestamp"].ToObject<long>();
long webTimestamp = webLastUpdatedJson["timestamp"].ToObject<long>();
if (webTimestamp <= localTimestamp)
{
return;
}
}
UpdateStageInternal(await LoadWebStages());
return;
}
}
var tempStage = new Dictionary<string, StageInfo>
{
// 这里会被 “剩余理智” 复用,第一个必须是 string.Empty 的
// 「当前/上次」关卡导航
{ string.Empty, new StageInfo { Display = LocalizationHelper.GetString("DefaultStage"), Value = string.Empty } },
};
private string GetClientType()
{
var clientType = ConfigurationHelper.GetValue(ConfigurationKeys.ClientType, string.Empty);
// 官服和B服使用同样的资源
@@ -108,28 +101,66 @@ namespace MaaWpfGui.Services
clientType = "Official";
}
// Download the activities
const string StageApi = "gui/StageActivity.json";
JObject activity = fromWeb
? _maaApiService.RequestMaaApiWithCache(StageApi).ConfigureAwait(false).GetAwaiter().GetResult()
: _maaApiService.LoadApiCache(StageApi);
return clientType;
}
// Download the tasks resources into cache so MaaCore can load them later
var tasksPath = "resource/tasks.json";
JObject tasksJson = fromWeb
? _maaApiService.RequestMaaApiWithCache(tasksPath).ConfigureAwait(false).GetAwaiter().GetResult()
: _maaApiService.LoadApiCache(tasksPath);
private JObject LoadLocalStages()
{
JObject activity = _maaApiService.LoadApiCache(StageApi);
JObject tasksJson = _maaApiService.LoadApiCache(TasksApi);
return activity;
}
private async Task<bool> CheckWebUpdate()
{
// Check if we need to update from the web
string lastUpdateTimeFile = "lastUpdateTime.json";
JObject localLastUpdatedJson = _maaApiService.LoadApiCache(lastUpdateTimeFile);
JObject webLastUpdatedJson = await _maaApiService.RequestMaaApiWithCache(lastUpdateTimeFile).ConfigureAwait(false);
if (localLastUpdatedJson != null && webLastUpdatedJson != null)
{
long localTimestamp = localLastUpdatedJson["timestamp"].ToObject<long>();
long webTimestamp = webLastUpdatedJson["timestamp"].ToObject<long>();
if (webTimestamp <= localTimestamp)
{
return false;
}
}
return true;
}
private async Task<JObject> LoadWebStages()
{
var clientType = GetClientType();
JObject activity = await _maaApiService.RequestMaaApiWithCache(StageApi);
JObject tasksJson = await _maaApiService.RequestMaaApiWithCache(TasksApi);
if (clientType != "Official" && tasksJson != null)
{
tasksPath = "resource/global/" + clientType + '/' + tasksPath;
var tasksPath = "resource/global/" + clientType + '/' + TasksApi;
// Download the client specific resources only when the Official ones are successfully downloaded so that the client specific resource version is the actual version
// TODO: There may be an issue when the CN resource is loaded from cache (e.g. network down) while global resource is downloaded (e.g. network up again)
// var tasksJsonClient = fromWeb ? WebService.RequestMaaApiWithCache(tasksPath) : WebService.RequestMaaApiWithCache(tasksPath);
_ = _maaApiService.RequestMaaApiWithCache(tasksPath);
await _maaApiService.RequestMaaApiWithCache(tasksPath);
}
return activity;
}
private void UpdateStageInternal(JObject activity)
{
var tempStage = new Dictionary<string, StageInfo>
{
// 这里会被 “剩余理智” 复用,第一个必须是 string.Empty 的
// 「当前/上次」关卡导航
{ string.Empty, new StageInfo { Display = LocalizationHelper.GetString("DefaultStage"), Value = string.Empty } },
};
var clientType = GetClientType();
bool isDebugVersion = Marshal.PtrToStringAnsi(AsstGetVersion()) == "DEBUG VERSION";
bool curVerParsed = SemVersion.TryParse(Marshal.PtrToStringAnsi(AsstGetVersion()), SemVersionStyles.AllowLowerV, out var curVersionObj);

View File

@@ -1,4 +1,4 @@
// <copyright file="HttpService.cs" company="MaaAssistantArknights">
// <copyright file="HttpService.cs" company="MaaAssistantArknights">
// MaaWpfGui - A part of the MaaCoreArknights project
// Copyright (C) 2021 MistEO and Contributors
//
@@ -163,7 +163,7 @@ namespace MaaWpfGui.Services.Web
var success = true;
try
{
var stream = response.Content.ReadAsStreamAsync().ConfigureAwait(false).GetAwaiter().GetResult();
var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
using var fileStream = new FileStream(fullFilePathWithTemp, FileMode.Create, FileAccess.Write);
// 记录初始化

View File

@@ -1749,14 +1749,10 @@ namespace MaaWpfGui.ViewModels.UI
/// <summary>
/// Updates manually.
/// </summary>
// TODO: 你确定要用 async void 不是 async Task
public async void ManualUpdate()
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task ManualUpdate()
{
var task = Task.Run(() =>
{
return _versionUpdateViewModel.CheckAndDownloadUpdate(true);
});
var ret = await task;
var ret = await _versionUpdateViewModel.CheckAndDownloadUpdate(true);
string toastMessage = null;
switch (ret)

View File

@@ -22,7 +22,7 @@ using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Threading;
using MaaWpfGui.Constants;
using MaaWpfGui.Extensions;
using MaaWpfGui.Helper;
@@ -171,12 +171,11 @@ namespace MaaWpfGui.ViewModels.UI
}
*/
private readonly Timer _timer = new Timer();
private readonly DispatcherTimer _timer = new DispatcherTimer();
private void InitTimer()
{
_timer.Enabled = true;
_timer.Interval = 1000 * 50;
_timer.Interval = TimeSpan.FromSeconds(50);
_timer.Tick += Timer1_Elapsed;
_timer.Start();
}
@@ -185,11 +184,14 @@ namespace MaaWpfGui.ViewModels.UI
{
if (NeedToUpdateDatePrompt())
{
Execute.OnUIThread(() =>
Task.Run(async () =>
{
_stageManager.UpdateStage(true);
UpdateDatePrompt();
UpdateStageList(false);
await _stageManager.UpdateStageWeb();
Execute.OnUIThread(() =>
{
UpdateDatePrompt();
UpdateStageList(false);
});
});
}
@@ -204,7 +206,7 @@ namespace MaaWpfGui.ViewModels.UI
{
if (_settingsViewModel.UpdatAutoCheck)
{
_settingsViewModel.ManualUpdate();
Task.Run(_settingsViewModel.ManualUpdate);
}
}

View File

@@ -20,6 +20,7 @@ using System.IO.Compression;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
@@ -357,14 +358,13 @@ namespace MaaWpfGui.ViewModels.UI
/// </summary>
/// <param name="force">是否强制检查。</param>
/// <returns>操作成功返回 <see langword="true"/>,反之则返回 <see langword="false"/>。</returns>
public CheckUpdateRetT CheckAndDownloadUpdate(bool force = false)
public async Task<CheckUpdateRetT> CheckAndDownloadUpdate(bool force = false)
{
_settingsViewModel.IsCheckingForUpdates = true;
var checkResult = ((Func<CheckUpdateRetT>)(() =>
{
async Task<CheckUpdateRetT> CheckUpdateInner() {
// 检查更新
var checkRet = CheckUpdate(force);
var checkRet = await CheckUpdate(force);
if (checkRet != CheckUpdateRetT.OK)
{
return checkRet;
@@ -507,7 +507,9 @@ namespace MaaWpfGui.ViewModels.UI
}
return CheckUpdateRetT.OK;
}))();
}
var checkResult = await CheckUpdateInner();
_settingsViewModel.IsCheckingForUpdates = false;
return checkResult;
@@ -537,7 +539,7 @@ namespace MaaWpfGui.ViewModels.UI
/// </summary>
/// <param name="force">是否强制检查。</param>
/// <returns>检查到更新返回 <see langword="true"/>,反之则返回 <see langword="false"/>。</returns>
private CheckUpdateRetT CheckUpdate(bool force = false)
private async Task<CheckUpdateRetT> CheckUpdate(bool force = false)
{
// 自动更新或者手动触发
if (!(_settingsViewModel.UpdateCheck || force))
@@ -558,7 +560,7 @@ namespace MaaWpfGui.ViewModels.UI
{
// 稳定版更新使用主仓库 /latest 接口
// 直接使用 MaaRelease 的话30 个可能会找不到稳定版,因为有可能 Nightly 发了很多
var stableResponse = RequestGithubApi(StableRequestUrl, RequestRetryMaxTimes);
var stableResponse = await RequestGithubApi(StableRequestUrl, RequestRetryMaxTimes);
if (string.IsNullOrEmpty(stableResponse))
{
return CheckUpdateRetT.NetworkError;
@@ -566,7 +568,7 @@ namespace MaaWpfGui.ViewModels.UI
_latestJson = JsonConvert.DeserializeObject(stableResponse) as JObject;
_latestVersion = _latestJson["tag_name"].ToString();
stableResponse = RequestGithubApi(MaaReleaseRequestUrlByTag + _latestVersion, RequestRetryMaxTimes);
stableResponse = await RequestGithubApi(MaaReleaseRequestUrlByTag + _latestVersion, RequestRetryMaxTimes);
// 主仓库能找到版,但是 MaaRelease 找不到,说明 MaaRelease 还没有同步(一般过个十分钟就同步好了)
if (string.IsNullOrEmpty(stableResponse))
@@ -579,7 +581,7 @@ namespace MaaWpfGui.ViewModels.UI
else
{
// 非稳定版更新使用 MaaRelease/releases 接口
var response = RequestGithubApi(RequestUrl, RequestRetryMaxTimes);
var response = await RequestGithubApi(RequestUrl, RequestRetryMaxTimes);
if (string.IsNullOrEmpty(response))
{
return CheckUpdateRetT.NetworkError;
@@ -636,7 +638,7 @@ namespace MaaWpfGui.ViewModels.UI
// 非稳定版本是 Nightly 下载的,主仓库没有它的更新信息,不必请求
if (isStdVersion(_latestVersion))
{
var infoResponse = RequestGithubApi(InfoRequestUrl + _latestVersion, RequestRetryMaxTimes);
var infoResponse = await RequestGithubApi(InfoRequestUrl + _latestVersion, RequestRetryMaxTimes);
if (string.IsNullOrEmpty(infoResponse))
{
return CheckUpdateRetT.FailedToGetInfo;
@@ -672,7 +674,7 @@ namespace MaaWpfGui.ViewModels.UI
}
}
private string RequestGithubApi(string url, int retryTimes)
private async Task<string> RequestGithubApi(string url, int retryTimes)
{
string response = string.Empty;
string[] requestSource = { "https://api.github.com/", "https://api.kgithub.com/" };
@@ -680,7 +682,8 @@ namespace MaaWpfGui.ViewModels.UI
{
for (var i = 0; i < requestSource.Length; i++)
{
response = _httpService.GetStringAsync(new Uri(requestSource[i] + url)).ConfigureAwait(false).GetAwaiter().GetResult();
// prevent current thread
response = await _httpService.GetStringAsync(new Uri(requestSource[i] + url)).ConfigureAwait(false);
if (!string.IsNullOrEmpty(response))
{
break;