Как прервать выполнение Task если метод в другом классе?
Доброго времени суток!
Пытаюсь подружить один прибор (векторный анализатор цепей) и программу(WPF) для обработки и расчетов. Все измерения проводятся в классе AutoMeasure. Нажимаю кнопочку на форме - создается объект класса AutoMeasure и далее в нем вызывается метод в отдельном Task, который производит измерение на приборе посредством SCPI комманд.
private void OnQuality0MeasurmentCommandExecuted(object p)
{
Task taskQ0Measure = new Task(() => DeviceResponse = autoMeasure.QualityMeasurment().ToString());
try
{
taskQ0Measure.Start(); // запускаю таск с методом измерения
/////////// ПРОБЛЕМНОЕ МЕСТО ///////////////////////////////////////
Task.WaitAll(taskQ0Measure); // надо подождать когда закончиться все
F0n = autoMeasure.FrequnecyCenter / 1e9;
F0low = autoMeasure.FrequnecyLow / 1e9;
F0high = autoMeasure.FrequnecyHigh / 1e9;
Q0n = autoMeasure.QualityFactor;
catch (Exception ex)
{
System.Windows.MessageBox.Show(ex.Message, "Ошибка ВАЦ!", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
В указанном проблемном месте - может возникнуть ситуация, когда прибор по каким то причинам отвалился/отключился/сломался/сгорел.
В классе AutoMeasure есть публичное свойство ViStatus status для отслеживания состояния прибора
class AutoMeasure
{
public double FrequnecyCenter { get; set; }
public double FrequnecyLow { get; set; }
public double FrequnecyHigh { get; set; }
public object DeviceResponse { get; set; }
public StringBuilder buff = new StringBuilder(1000);
public ViSession rm, rsrc;
public ViStatus status;
public AutoMeasure()
{
} ....далее все методы.......
}
В свойстве ViStatus status - во время успешной работы постоянно присваивается строка "VI_SUCCES" либо что то другое, когда ошибка. Иными словами условие прерывания в проблемном месте должно быть что то типа
if (status!="VI_SUCCES") // то прерываем выполнение всех методов в классе AutoMeasure
Вопрос в том, как и куда что добавить (токен отмены? в MainWindowViewModel, или в класс AutoMeasure? или еще куда то) или как то по другому решить задачу, чтобы когда прибор "отвалился" - программа не зависала. Так как когда прибор отваливается, внутри метода autoMeasure.QualityMeasurment() - происходит очень много кода и комманд SCPI, там внутри писать проверки и завершение метода в случае ошибки - это очень гемморная и долгая работа но status вроде всегда обновляется и всегда доступен снаружи.
Что можно предпринять в данной ситуации? Заранее всем спасибо)
******* Добавляю больше кода)
// XAML Код кнопки
<Button
Style="{DynamicResource ButtonRoundVNA}"
FontSize="12" FontWeight="Light"
Background="#D99594"
Command="{Binding Quality0Measurment}" // комманда запуска измерения
IsEnabled="{Binding VNATrigger}" > // тригер разблокировки/блокировки кнопки
<Button.Content>
<TextBlock Margin="0,-2,0,0" FontSize="14" FontWeight="DemiBold">
Запуск измерения <Run FontWeight="Bold" FontStyle="Italic" > R0</Run>
</TextBlock>
</Button.Content>
</Button>
//////////////////////////////////////////
// Код в MainWindowViewModel
public ICommand Quality0Measurment { get; }
private void OnQuality0MeasurmentCommandExecuted(object p)
{
CancellationTokenSource cts = new CancellationTokenSource();
Task taskQ0Measure = new Task(() => DeviceResponse = autoMeasure.QualityMeasurment().ToString(),cts.Token);
try
{
taskQ0Measure.Start();
/////// ПРОБЛЕМНОЕ МЕСТО /////// - куда здесь воткнуть токен?
Task.WaitAll(taskQ0Measure);
bool succesQ = autoMeasure.QualityMeasureCheck(FLow:autoMeasure.FrequnecyLow, FHigh:autoMeasure.FrequnecyHigh, FCenter:autoMeasure.FrequnecyCenter);
if (succesQ == true)
{
F0n = autoMeasure.FrequnecyCenter / 1e9; //
F0low = autoMeasure.FrequnecyLow / 1e9;
F0high = autoMeasure.FrequnecyHigh / 1e9;
Q0n = autoMeasure.QualityFactor;
}
else
{
F0n = -1;
F0low = -1;
F0high = -1;
Q0n = -1;
}
}
catch (Exception ex)
{
System.Windows.MessageBox.Show(ex.Message, "Ошибка ВАЦ!", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private bool CanQuality0MeasurmentCommandExecute(object p) => true;
public MainWindowViewModel()
{
Quality0Measurment = new LambdaCommand(OnQuality0MeasurmentCommandExecuted, CanQuality0MeasurmentCommandExecute);
}
///// Класс AutoMeasure
class AutoMeasure
{
public double FrequnecyCenter { get; set; }
public double FrequnecyLow { get; set; }
public double FrequnecyHigh { get; set; }
public double QualityFactor { get; set; }
public object DeviceResponse { get; set; }
public StringBuilder buff = new StringBuilder(1000);
public ViSession rm, rsrc;
public ViStatus status;
public AutoMeasure()
{
}
public object QualityMeasurment()
{
///// здесь идут SCPI комманды для общение с прибором, во время комманды или запроса может произойти зависание, и status становиться != "VI_SUCCES"
status = Visa.Printf(rsrc, "*CLS\n");
status = Visa.Printf(rsrc, ":INITiate:IMMediate\n");
status = Visa.Queryf(rsrc, "*OPC?\n", "%T", __arglist(buff));
status = Visa.Printf(rsrc, ":DISPlay:WINDow0:TRACe0:Y[:SCALe]:AUTO\n");
// Комманды и дополнительных методов с SCPI коммандами тут исопльзуется значительно больше
///////////////////////////////////
// в итоге после успешного завершения всех комманд
FrequnecyCenter = FCentrView;
FrequnecyHigh = Fhighaproxim;
FrequnecyLow = FLowView;
QualityFactor = QView;
DeviceResponse = status;
return DeviceResponse;
}
}
internal class LambdaCommand : Command
{
private readonly Action<object> _Execute;
private readonly Func<object, bool> _CanExecute;
public LambdaCommand(Action<object> Execute, Func<object, bool> CanExecute = null)
{
_Execute = Execute ?? throw new ArgumentNullException(nameof(Execute));
_CanExecute = CanExecute;
}
public override bool CanExecute(object parameter) => _CanExecute?.Invoke(parameter) ?? true;
public override void Execute(object parameter) => _Execute(parameter);
}
//Успешное выполнение SCPI комманд в коде во время отладки

Ответы (1 шт):
Начнем с интерфейса. Ваша LambdaCommand хорошая, но она не умеет реагировать на ввод. Есть фишка, что когда вы жмете в окно мышкой или клавиатурой, можно обновлять CanExecute команды автоматически, класс дотнета CommandManager будет сам заниматься этим делом. Главное - правильно к нему привязаться.
Вот альтернатива, которую я утащил у Microsoft и немного причесал код.
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
=> (_execute, _canExecute) = (execute, canExecute);
public bool CanExecute(object parameter)
=> _canExecute == null || _canExecute(parameter);
public void Execute(object parameter)
=> _execute(parameter);
}
То есть здесь наследуем не класс Command (кстати, что это?), а реализуем интерфейс ICommand полностью.
CanExecute - это блокировщик доступности кнопки. То есть вот это IsEnabled="{Binding VNATrigger}" - надо выкинуть, чтобы не мешать CanExecute команды работать.
<Button
Style="{DynamicResource ButtonRoundVNA}"
FontSize="12" FontWeight="Light"
Background="#D99594"
Command="{Binding Quality0Measurment}">
<Button.Content>
<TextBlock Margin="0,-2,0,0" FontSize="14" FontWeight="DemiBold">
Запуск измерения <Run FontWeight="Bold" FontStyle="Italic"> R0</Run>
</TextBlock>
</Button.Content>
</Button>
Теперь работа с тасками и девайсом. Как я вижу, команды синхронные, то есть запустить в потоке можно. Еще вы утверждаете, что статус меняется, если что-то пошло не так. То есть сбой происходит до того как происходит зависание. Следовательно, вам стоит проверять статус после каждой команды. И если что-то пошло не так - сразу же бросать исключение. Давайте кстати его создадим.
public class AutoMeasureException : Exception
{
public ViStatus Status { get; }
public AutoMeasureException(ViStatus status) : base(status.ToString())
{
Status = status;
}
}
Тогда можно будет реализовать выброс исключения при любом изменении статуса через свойство, не особо изменяя ваш существующий код.
class AutoMeasure
{
public double FrequnecyCenter { get; set; }
public double FrequnecyLow { get; set; }
public double FrequnecyHigh { get; set; }
public double QualityFactor { get; set; }
public ViStatus DeviceResponse { get; set; }
public StringBuilder buff = new StringBuilder(1000);
public ViSession rm, rsrc;
private ViStatus status;
public ViStatus Status {
get => status;
set
{
status = value;
if (status != ViStatus.VI_SUCCESS) // можно дописать || !status.ToString().StartsWith("VI_SUCCESS_")
throw new AutoMeasureException(value);
}
}
public AutoMeasure()
{
}
public ViStatus QualityMeasurment()
{
Status = Visa.Printf(rsrc, "*CLS\n");
Status = Visa.Printf(rsrc, ":INITiate:IMMediate\n");
Status = Visa.Queryf(rsrc, "*OPC?\n", "%T", __arglist(buff));
Status = Visa.Printf(rsrc, ":DISPlay:WINDow0:TRACe0:Y[:SCALe]:AUTO\n");
// ...
FrequnecyCenter = FCentrView;
FrequnecyHigh = Fhighaproxim;
FrequnecyLow = FLowView;
QualityFactor = QView;
DeviceResponse = Status;
return DeviceResponse;
}
}
Теперь зависания не произойдет, так как выполнение метода будет немедленно остановлено выбросом исключения как только результат выполнения одной из команд будет отличный от успешного.
Теперь вернемся к команде. Будем так же запускать поток, только немного правильнее, без подвешивания интерфейса на время, пока он работает и дизаблить кнопку на время работы. При этом остальное приложение останется живым на время выполнения, даже если где-то в потоке что-то зависнет.
Теперь с новой командой напишу лямбду с "ленивой" инициализацией. И немного асинхронного программирования.
public bool VNATrigger { get; set; } // в свойствах у вас INPC реализация, не буду ее сюда дублировать
public ViStatus DeviceResponse { get; set; } // WPF UI прекрасно покажет значение перечисления без всяких ToString()
private ICommand _quality0Measurment;
public ICommand Quality0Measurment => _quality0Measurment ??= new RelayCommand(async parameter =>
{
VNATrigger = false;
try
{
DeviceResponse = await Task.Run(() => autoMeasure.QualityMeasurment()); // вот так вот просто
bool succesQ = autoMeasure.QualityMeasureCheck(FLow: autoMeasure.FrequnecyLow, FHigh: autoMeasure.FrequnecyHigh, FCenter: autoMeasure.FrequnecyCenter);
if (succesQ)
{
F0n = autoMeasure.FrequnecyCenter / 1e9; //
F0low = autoMeasure.FrequnecyLow / 1e9;
F0high = autoMeasure.FrequnecyHigh / 1e9;
Q0n = autoMeasure.QualityFactor;
}
else
{
F0n = -1;
F0low = -1;
F0high = -1;
Q0n = -1;
}
}
catch (AutoMeasureException ex)
{
// то есть тот самый Status из AutoMeasure приехал сюда. ex.Message тоже есть, допишите класс AutoMeasureException под себя
MessageBox.Show(ex.Status.ToString(), "Ошибка ВАЦ!", MessageBoxButton.OK, MessageBoxImage.Error);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
}
VNATrigger = true;
CommandManager.InvalidateRequerySuggested(); // эта команда заставляет CommandManager перезапросить CanExecute
}, parameter => VNATrigger);
CancellationToken по сути здесь и не нужен, так как отмена происходит только изнутри, не снаружи.
