Баг проявляется не всегда и, видимо, зависит от времени выполнения асинхронной операции. В обычном рантайме программа просто зависает, при дебаге удается получить только стандартное сообщение о доступе из другого потока. Демонстрация на видео:
https://youtu.be/DdEzrM86MJk
Форма создается следующим образом. Вначале получаю объект вью-модели, который назначаю в качестве DataContext, затем показываю форму:
public override void ShowDataRecordForm(DataDomainMetadata md, int id)
{
var obj = new DataDomainObject(md, id);
DataRecordContext ctx = new(obj);
var form = GetForm(ctx);
form.Owner = App.MainFormStatic;
form.StartPosition = FormStartPosition.CenterScreen;
form.Show();
}
В конструкторе вью-модели запускается таск:
public DataRecordContext(DataDomainMetadata md, int id)
: this(md)
{
DataLoadStatus = DataLoadStatusEnum.NotLoaded;
Task.Factory.StartNew(() => RunDataLoadAsync(id, _cts.Token), TaskCreationOptions.LongRunning);
//Task.Run(() => RunDataLoadAsync(id, _cts.Token));
}
Сама асинхронная операция выглядит так:
async Task RunDataLoadAsync(int id, CancellationToken ct)
{
try
{
OnDataLoadStatusChanged(DataLoadStatusEnum.OnLoading);
var row = await Provider.GetDataRecordRowAsync(id);
if (row == null)
return;
if (ct.IsCancellationRequested)
return;
SetDataRow(row);
}
catch
{
OnDataLoadStatusChanged(DataLoadStatusEnum.Error);
}
}
Когда форма получает контекст (вью-модель), все дочерние контролы обрабатывают событие ContextChanged:
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
if (DataContext is IAsyncViewModel asyncViewModel)
{
//Эта задержка скрывает проблему, но не решает ее
//await Task.Delay(100);
if (asyncViewModel.IsDataLoaded)
{
BindValue();
}
else
{
SetLoading();
}
asyncViewModel.DataLoadStatusChanged += DataContext_DataLoadStatusChanged;
}
}
Если на момент загрузки строкового контрола вью-модель готова (IsDataLoaded == true), сразу выполняется биндинг. Если нет, биндинг будет обработан в подписке. Но поскольку делегат будет вызываться из RunDataLoadAsync, который в другом потоке, выполняю проверку InvokeRequired:
void DataContext_DataLoadStatusChanged(object? sender, DataLoadStatusChangedEventArgs e)
{
void action()
{
if (DataContext is IAsyncViewModel)
{
switch (e.Status)
{
case DataLoadStatusEnum.OnLoading:
{
SetLoading();
break;
}
case DataLoadStatusEnum.Loaded:
{
BindValue();
SetLoaded();
break;
}
case DataLoadStatusEnum.Error:
{
SetError();
break;
}
}
}
}
if (InvokeRequired)
{
Invoke(() => action());
}
else
{
action();
}
}
Я попробовал воспроизвести этот баг на тестовом проекте (
github), но там она себя не проявляет.
P.S. Знаю, что у этого кода есть проблема с тем, что между проверкой на IsDataLoaded и подпиской на событие существует лаг, за который RunDataLoadAsync может вызвать OnDataLoadStatusChanged и контрол никогда не выйдет из статуса загрузки, но вряд ли это относится к проблеме.