Member 11577008 Ответов: 0

Как предотвратить несколько задач await при вызове асинхронного метода более одного раза


Я переписываю приложение, которое в настоящее время развернуто на заводе под управлением 3D-тепловизора и программного обеспечения, смонтировано на промышленном роботе и взаимодействует с ним, а также с базой данных. Приложение связывает все 3 системы вместе для контроля размеров деталей, выходящих из процесса. Хотя он работает, его нелегко поддерживать, и пользовательский интерфейс не реагирует во время процесса измерения.

Я значительно упростил приложение и перешел на шаблон MVVM, но мне нужно улучшить общее время цикла и сохранить пользовательский интерфейс отзывчивым и плавным.

Каждый раз, когда я вызываю асинхронный метод Write (Tag tag, строковое значение) Я получаю новую задачу, запущенную вместе со всеми предыдущими задачами, которые снова запускаются.

Я не понимаю, как правильно распорядиться задачей после ее завершения?

ЗАПИСКА: "Свойства.Настройки.По умолчанию.PlcBypass"в настоящее время установлено значение True, пока я не отлажу это и не смогу опробовать его с помощью ПЛК. (Это слишком рискованно, когда играешь с промышленным роботом в производстве)

Кроме того, я проверяю tag.Name = = " контроль" потому что есть и другие типы тегов, которые должны обрабатываться по-другому.

Класс Tag на самом деле является просто INotifyable типом данных с 3 свойствами

Вот асинхронный код:
public async void Write(Tag tag, string value)
{
    tag.Value =  (await Task.Run(() => ToPlc(tag, value))).ToString();
}

private int ToPlc(Tag tag, string value)
{
    Thread.Sleep(3000);
    int result = int.Parse(value);
    try
    {
        _plc.Write(tag.TagAddress, value);
    }
    catch (Exception ex)
    {
        if (tag.Name == "Control" && Properties.Settings.Default.PlcBypass)
        {
            if (result < 11)
            {
                result++;
            }
            else result = 0;
        }
        else throw new Exception(string.Format("WRITE FAILED FOR {0}: {1}", tag.Name, ex.Message));
    }
    return result;
}

Опять же, задачи выполняются асинхронно, но я получаю новую задачу каждый раз, когда вызываю асинхронный метод, и все старые задачи возобновляются, все параллельно, каждый посылает различные значения ПЛК (роботу)

Что я уже пробовал:

Я пробовал различные способы понять это, вплоть до написания приложения просто для "игры" с асинхронными задачами.

Приведенный ниже код настолько прост, насколько я думаю, что вы можете получить, но все же включает в себя CancellationTokens и отчеты о ходе выполнения. Он также был изменен, чтобы вести учет повторяющихся задач.

Файл MainWindow.язык XAML:
<Window x:Class="Task_basedAsynchronousPatternTAP.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Task_basedAsynchronousPatternTAP"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <StackPanel Orientation="Vertical" Margin="10,20,10,10">
            <StackPanel Orientation="Horizontal">
                <TextBlock Name="MyClock" Text="CurrentTime" FontSize="20" Width="240" Height="30" HorizontalAlignment="Left" VerticalAlignment="Top"/>
                <TextBox Name="TaskLengthValue" Width="90" FontSize="20" TextAlignment="Center" Text="500" />
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <Button x:Name="buttonStart" Content="Start" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Click="buttonStart_Click"/>
                <Button x:Name="buttonStop" Content="Stop" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Click="buttonStop_Click"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
                <TextBlock x:Name="textBlock1" TextWrapping="Wrap"  FontSize="60" Width="340" Height="80" Text="TextBlock" TextAlignment="Center"/>
            </StackPanel>
            <Label x:Name="label1" Content="Label" HorizontalContentAlignment="Center"   FontSize="30"/>
            <Label x:Name="labelResult" Content="Result" HorizontalContentAlignment="Center"   FontSize="30"/>
        </StackPanel>
    </Grid>
</Window>


Файл MainWindow.язык XAML.в CS

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;

namespace Task_basedAsynchronousPatternTAP
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// Progress Reporting:  https://social.technet.microsoft.com/wiki/contents/articles/19020.progress-of-a-task-in-c.aspx
    /// Codeproject question post: https://www.codeproject.com/Questions/1185488/TAP-my-task-has-completed-how-do-I-remove-it
    /// 
    /// </summary>
    public partial class MainWindow : Window
    {
        //private CancellationToken ct;
        private CancellationTokenSource ts;
        private readonly SynchronizationContext synchronizationContext;
        public string CurrentTime { get; set; }
        public MainWindow()
        {
            InitializeComponent();
            textBlock1.Text = "-2";
            synchronizationContext = SynchronizationContext.Current;

            DispatcherTimer timer = new DispatcherTimer(new TimeSpan(0, 0, 0, 0, 25), DispatcherPriority.Normal, delegate
            {
                this.MyClock.Text = DateTime.Now.ToString("HH:mm:ss.fff");
            }, this.Dispatcher);
        }


        private DateTime previousTime = DateTime.Now;
        private async void buttonStart_Click(object sender, RoutedEventArgs e)
        {
            labelResult.Content = "RUNNING";
            ts = new CancellationTokenSource();
            CancellationToken ct = ts.Token;
            textBlock1.Text = "1";
            int v = int.Parse(TaskLengthValue.Text);
            var progress = new Progress<MyTaskProgressReport>();
            progress.ProgressChanged += (o, report) =>
            {
                textBlock1.Text = report.TotalProgressAmount.ToString();
                if (report.TotalProgressAmount % 10 == 0)
                {
                    label1.Content = report.CurrentProgressMessage;
                }
                report = null;
            };

            int result = await LongRunningOperationAsync(ct, v, progress);
            
            if (ts.IsCancellationRequested)
            {
                labelResult.Content = string.Format("RESULT: Stopped At {0} Ticks", result.ToString());
            }
            else
            {
                labelResult.Content = string.Format("RESULT: Completed!", result.ToString());
            }
        }

        Task<int> LongRunningOperationAsync(CancellationToken ct, int v, Progress<MyTaskProgressReport> p)
        {
            LengthyOperation lo = new LengthyOperation(ct, p);
            //create a task completion source
            //the type of the result value must be the same
            //as the type in the returning Task
            TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
            Task.Run(() =>
            {
                //set the result to TaskCompletionSource
                tcs.SetResult(lo.Start(v));
            });
            //return the Task
            return tcs.Task;
        }

        private void buttonStop_Click(object sender, RoutedEventArgs e)
        {
             ts.Cancel();
        }
    }
}


LengthyOperation. cs: обновлено 19 мая Большое спасибо G3Coder :

Цитата:
В классе LengthyOperation у вас есть статический таймер, а в ctor вы добавляете истекшие события (+=) к этому единственному статическому таймеру. Таким образом, когда вы включаете таймер для последующих операций lengthy в ctor, все события все еще прикрепляются, а затем запускаются, что приводит к путанице на переднем конце. Я предлагаю вам сделать таймер нестатическим частным полем класса LengthyOperation и протестировать его.


Новый код: (старый код ниже Для справки)

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Task_basedAsynchronousPatternTAP
{
    class LengthyOperation
    {
        private CancellationToken ct;
        private string taskID;
        public int Ticks { get; set; }
        IProgress<MyTaskProgressReport> progress;
        public LengthyOperation(CancellationToken _ct, IProgress<MyTaskProgressReport> _pgrs)
        {
            ct = _ct;
            progress = _pgrs;
            taskID = Task.CurrentId.ToString();
        }

        private void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            Ticks++;
            MyTaskProgressReport statusReport = new MyTaskProgressReport { Name = "LengthyOperation", ThisTaskID = taskID, CurrentProgressAmount = 1, TotalProgressAmount = Ticks, CurrentProgressMessage = string.Format("Operation Step: {0}, TaskID: {1}", Ticks, taskID) };
            progress.Report(statusReport);
            statusReport = null;
        }

        public int Start(int l)
        {
            Ticks = 0;
            progress.Report(new MyTaskProgressReport { Name = "LengthyOperationStarted", ThisTaskID = taskID, CurrentProgressAmount = 1, TotalProgressAmount = Ticks, CurrentProgressMessage = string.Format("Operation Step: {0}, TaskID: {1}", Ticks, taskID) });
            System.Timers.Timer _timer = new System.Timers.Timer();
            _timer.Interval = 150;
            _timer.Elapsed += _timer_Elapsed;
            _timer.Enabled = true;
            taskID = Task.CurrentId.ToString();
            while (Ticks < l)
            {
                if (ct.IsCancellationRequested) break;
                Thread.Sleep(10);
            }
            _timer.Enabled = false;
            if (ct.IsCancellationRequested)
            {
                progress.Report(new MyTaskProgressReport { Name = "LengthyOperationCancled", ThisTaskID = taskID, CurrentProgressAmount = 1, TotalProgressAmount = Ticks, CurrentProgressMessage = string.Format("Operation Step: {0}, TaskID: {1}", Ticks, taskID) });
            }
            else
            {
                progress.Report(new MyTaskProgressReport { Name = "LengthyOperationCompleted", ThisTaskID = taskID, CurrentProgressAmount = 1, TotalProgressAmount = Ticks, CurrentProgressMessage = string.Format("Operation Step: {0}, TaskID: {1}", Ticks, taskID) });
            }
            return Ticks;
        }
    }
    public class MyTaskProgressReport
    {
        //task name
        public string Name { get; set; }
        // TaskID
        public string ThisTaskID { get; set; }
        //current progress
        public int CurrentProgressAmount { get; set ; }
        //total progress
        public int TotalProgressAmount { get; set; }
        //some message to pass to the UI of current progress
        public string CurrentProgressMessage { get; set; }
    }
}


старый код:
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Task_basedAsynchronousPatternTAP
{
    class LengthyOperation
    {
        private static System.Timers.Timer _timer = new System.Timers.Timer();
        private CancellationToken ct;
        private string taskID;
        public int Ticks { get; set; }
        IProgress<MyTaskProgressReport> progress;
        public LengthyOperation(CancellationToken _ct, IProgress<MyTaskProgressReport> _pgrs)
        {
            ct = _ct;
            progress = _pgrs;
            _timer.Interval = 150;
            _timer.Elapsed += _timer_Elapsed;
            taskID = Task.CurrentId.ToString();
        }

        private void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            Ticks++;
            MyTaskProgressReport statusReport = new MyTaskProgressReport { Name = "LengthyOperation", ThisTaskID = taskID, CurrentProgressAmount = 1, TotalProgressAmount = Ticks, CurrentProgressMessage = string.Format("Operation Step: {0}, TaskID: {1}", Ticks, taskID) };
            progress.Report(statusReport);
            statusReport = null;
        }

        public int Start(int l)
        {
            _timer.Enabled = true;
            taskID = Task.CurrentId.ToString();
            while (Ticks < l)
            {
                if (ct.IsCancellationRequested) break;
                Thread.Sleep(10);
            }
            _timer.Enabled = false;
            if (ct.IsCancellationRequested)
            {
                progress.Report(new MyTaskProgressReport { Name = "LengthyOperationCancled", ThisTaskID = taskID, CurrentProgressAmount = 1, TotalProgressAmount = Ticks, CurrentProgressMessage = string.Format("Operation Step: {0}, TaskID: {1}", Ticks, taskID) });
            }
            else
            {
                progress.Report(new MyTaskProgressReport { Name = "LengthyOperationCompleted", ThisTaskID = taskID, CurrentProgressAmount = 1, TotalProgressAmount = Ticks, CurrentProgressMessage = string.Format("Operation Step: {0}, TaskID: {1}", Ticks, taskID) });
            }
            return Ticks;
        }
    }
    public class MyTaskProgressReport
    {
        //task name
        public string Name { get; set; }
        // TaskID
        public string ThisTaskID { get; set; }
        //current progress
        public int CurrentProgressAmount { get; set ; }
        //total progress
        public int TotalProgressAmount { get; set; }
        //some message to pass to the UI of current progress
        public string CurrentProgressMessage { get; set; }
    }
}

Member 11577008

Я забыл объяснить, что этот асинхронный метод вызывается из другого класса, который обрабатывает 3D-изображение... в настоящее время этот класс ждет от меня ввода "продолжить", поэтому я знаю, что эта часть в настоящее время синхронна, и они не работают друг над другом.

CHill60

Одна из причин, по которой вы не получили никаких ответов на свой предыдущий пост, заключается в том, что ты удалил его из списка неотвеченных постов, разместив решение! Если у вас есть дополнительная информация для добавления в ваш пост используйте Улучшить вопрос ссылка (становится видимой при наведении курсора мыши на ваш пост или щелчке / касании внутри вашего поста)

Member 11577008

Спасибо, я понял, что это была ошибка после того, как опубликовал ее, а затем понял, где находится ссылка на комментарий. Я не понимал, однако, что мой пост больше не был виден. Я читаю codeproject уже много лет, но, как вы, наверное, догадались, это мои первые просьбы о помощи.

CHill60

Он все еще "виден", но если кто-то использует список" неотвеченных вопросов", то он его не увидит. Многие люди, которые используют список" все вопросы", все равно пропустят его, потому что у него есть " решение"
Я вижу, что люди начали понижать голос вашего поста - я предлагаю вам отредактировать этот пост с помощью Улучшить вопрос ссылка чтобы переместить любую информацию из вашего другого поста в этот, удалите слово "срочно" из вашего заголовка и ссылки на другой пост из этого. Затем удалите свой другой пост - если, конечно, кто-то не ответил на него! Если вы не знаете, когда вы наводите курсор на свой вопрос или нажимаете на него, большой красный крест становится видимым в правом нижнем углу сообщения - именно так вы его удаляете (он не всегда виден)

CHill60

О, и избегайте употребления слова "срочно" - это может быть срочно для вас, но мы все здесь добровольцы. Есть также некоторые" большие " типы, которые будут игнорировать ваш пост потому что вы используете слово срочно. Просто чтобы ты знал.

Richard MacCutchan

Большие, мы?

CHill60

Ладно-тогда я ;)

[no name]

Я бы тоже хотел. Я плохо реагирую на грубых людей.

CHill60

ОП теперь понимает что это было грубо и извинился см. ниже ВВВ

[no name]

В прошлый раз, вчера, я сказал кому-то, что они были грубы (потому что он публиковал грубые комментарии другим людям), просто удалил свой пост. Затем он опустил все ответы, которые смог найти из моих. Это определенно показало мне, что он может быть вежливым и уважительным! Кстати, это была не операция.

CHill60

Да, я тоже был такой мишенью. Точно так же сайт ограничивает, сколько раз вы можете это сделать. Это раздражает, потому что дает другим посетителям неправильное представление о достоверности или точности решения (которое часто является принятым решением).
: Смех: я тоже был мишенью апвотера - наверное, я действительно кому - то помог, и они хотели сказать спасибо :)

Member 11577008

Спасибо, точка зрения принята, и я полностью понимаю, что те, кто помогает здесь, делают это добровольно и не хотели показаться требовательными.

G3Coder

Привет,

В классе LengthyOperation у вас есть статический таймер, а в ctor вы добавляете истекшие события (+=) к этому единственному статическому таймеру. Таким образом, когда вы включаете таймер для последующих длительных операций в ctor, все события все еще прикрепляются, а затем запускаются, что приводит к путанице на переднем конце. Я предлагаю вам сделать таймер нестатическим частным полем класса LengthyOperation и протестировать его.

Повеселиться
Г

Member 11577008

Это все исправило!!!!
Однако я все еще не совсем уверен, что полностью понимаю этот вопрос, но я точно обмотаю свой мозг вокруг него!

Большое вам спасибо!

G3Coder

Круто, рад помочь - просто помните, что статика принадлежит классу, а не экземпляру и его равнине оттуда..

Повеселиться
Г

Ralf Meier

У меня есть вопрос :
Почему бы вам не записать свои переменные в своего рода FIFO (First-In-First-Out), который работает с помощью задачи (или Backgroundworker)?
Таким образом (я знаю, что это совершенно другой подход) вы только заполняете список, и ваша система сама остается ответственной - задача выполняет связь с ПЛК (который обычно имеет свои собственные правила и работает синхронно) ...

0 Ответов