mirror of
https://github.com/MaaAssistantArknights/MaaAssistantArknights.git
synced 2026-07-17 01:59:33 +08:00
fix: deadlock in UI thread
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
// 记录初始化
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user