Форум программистов, компьютерный форум, киберфорум
C# .NET
Войти
Регистрация
Восстановить пароль
Карта форума Темы раздела Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.60/15: Рейтинг темы: голосов - 15, средняя оценка - 4.60
19 / 19 / 14
Регистрация: 06.08.2009
Сообщений: 533
1

Как правильно работать с асинхронными методами?

25.06.2018, 10:54. Показов 2927. Ответов 16
Метки нет (Все метки)

Author24 — интернет-сервис помощи студентам
Добрый день.
Разъясните, мне, пожалуйста, как правильно использовать асинхронные методы, если в программе есть и обычный код?
Например, делаю вот так:
C#
1
2
3
4
5
6
7
8
9
10
11
12
            res.time_beg = DateTime.Now;
            Binance.API.Csharp.Client.Models.Account.NewOrder result = null;
            OrderSide order_side = OrderSide.BUY;
            if (info.type.ToLower() == "sell") order_side = OrderSide.SELL;
            if (info.price == 0m)
            {
                result = binanceClient.PostNewOrder(info.ticker, info.count, info.price, order_side, Binance.API.Csharp.Client.Models.Enums.OrderType.MARKET).Result;
            }
            else
            {
                result = binanceClient.PostNewOrder(info.ticker, info.count, info.price, order_side).Result;
            }
где PostNewOrder асинхронный метод, реализованный вот так:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
        public async Task<NewOrder> PostNewOrder(string symbol, decimal quantity, decimal price, OrderSide side, OrderType orderType = OrderType.LIMIT, TimeInForce timeInForce = TimeInForce.GTC, decimal icebergQty = 0m, long recvWindow = 5000)
        {
            //Validates that the order is valid.
            ValidateOrderValue(symbol, orderType, price, quantity, icebergQty);
 
            var args = $"symbol={symbol.ToUpper()}&side={side}&type={orderType}&quantity={quantity}"
                + (orderType == OrderType.LIMIT ? $"&timeInForce={timeInForce}" : "")
                + (orderType == OrderType.LIMIT ? $"&price={price}" : "")
                + (icebergQty > 0m ? $"&icebergQty={icebergQty}" : "")
                + $"&recvWindow={recvWindow}";
            var result = await _apiClient.CallAsync<NewOrder>(ApiMethod.POST, EndPoints.NewOrder, true, args);
 
            return result;
        }
И почему то у меня виснет на строке result = binanceClient.PostNewOrder(info.ticker, info.count, info.price, order_side).Result;
Подскажите, пожалуйста, что я делаю не так?
0
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
25.06.2018, 10:54
Ответы с готовыми решениями:

Как сделать эти методы асинхронными?
public void Uploading() { try { dirInfo = new...

Как правильно работать с БД
Visual Studio 2015 VB.NET 4.5.2 + SQL Подскажите плиз след. вопрос: Есть у меня допустим...

Как правильно работать с АДО ?
Есть локальная база Access. При добавлении новых записей через один Recordset приходится долго...

Как правильно работать с сетками?
Подскажите как распланировать сетку как на мекете до этого накидывал просто готовые варианты. Потом...

16
Эксперт .NET
17688 / 12873 / 3366
Регистрация: 17.09.2011
Сообщений: 21,138
25.06.2018, 11:22 2
Лучший ответ Сообщение было отмечено kolorotur как решение

Решение

Цитата Сообщение от megabax Посмотреть сообщение
Подскажите, пожалуйста, что я делаю не так?
Совершаете одну из самых распространенных ошибок при работе с асинхронным кодом: мешаете вместе асинхронное (await) и синхронное (Result) ожидания, что зачастую приводит к зависанию приложения наглухо.

Чтобы этого избежать, в идеале сделайте метод, в котором вызывается PostNewOrder тоже асинхронным и замените обращение к свойству Result на await.
Если это по какой-то причине невозможно (например, вызов происходит в аксессоре или в реализации интерфейса), то запускайте вызов PostNewOrder в одном из потоков пула через Task.Run и там уже блокируйте поток вызовом Result.
Так же в асинхронных методах, в которых контекст выполнения не имеет значения, имеет смысл в асинхронных вызовах добавлять ConfigureAwait(false) — это немного улучшит производительность и может уменьшить шанс зависания, но последнее, конечно, не гарантируется.
0
19 / 19 / 14
Регистрация: 06.08.2009
Сообщений: 533
25.06.2018, 12:12  [ТС] 3
Цитата Сообщение от kolorotur Посмотреть сообщение
Если это по какой-то причине невозможно (например, вызов происходит в аксессоре или в реализации интерфейса)
Вот как раз мой случай.
У меня есть некий алгоритм, который тестирует другой алгоритм на заранее загруженных данных (так называемый бэктестинг). А далее необходимо сделать, чтобы он делал то же самое, но на реальных данных, поступающих в режиме реального времени, но несколько другим способов (сначала был эмулятор, а затем некий реальный объект, который совершает некие действия, в данном случае это торговля на бирже). Чтобы не переписывать всю систему, я как раз и реализовал все это через интерфейс, а вот сейчас выяснилось, что библиотека, работающая с реальной биржей, работает через асинхронные методы.
Как тут лучше всего поступить? В соответствующем методе вызывать асинхронный метод бес параметров и каким-то волшебным образом ждать пока он отработает и вернет значения, но ждать не более аймаута, или все таки лучше перелопатить интерфейс и соответственно, сам эмулятор под асинхронные методы.
Как я заметил, вот такое вот дело:
C#
1
2
3
4
5
6
7
8
9
        /// <summary>
        /// Обоновить информацию о счете
        /// </summary>
        public async void refresh_account_info()
        {
            loader.load();
            long recvWindow = 5000;
            account_info = await apiClient.CallAsync<AccountInfo>(ApiMethod.GET, EndPoints.AccountInformation, true, $"recvWindow={recvWindow}", loader.unix_time);
        }
с последующим вызовом кода (в другое время):
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
        public List<StockItem> GetPositions()
        {
            if (apiClient == null) throw new Exception("BinanceDriver.GetPositions() - не устанволено соединение с биржей");
            List <StockItem> res = new List<StockItem>();
            if(account_info!=null)
            {
                foreach (Balance balance in account_info.Balances)
                {
                    StockItem item = new StockItem();
                    item.name = balance.Asset;
                    item.count = balance.Free;
                    res.Add(item);
                }                
            }
            return res;
        }
Вполне прокатывает.
0
Эксперт .NET
17688 / 12873 / 3366
Регистрация: 17.09.2011
Сообщений: 21,138
25.06.2018, 13:06 4
Цитата Сообщение от megabax Посмотреть сообщение
Как тут лучше всего поступить?
Если имеется возможность, я бы изменил сигнатуры своих интерфейсов на асинхронные — наиболее приемлемый и наименее костыльный вариант.
Например, было так:
C#
1
2
3
4
public interface ISomething
{
   SomeClass SomeMethod(SomeType someParam);
}
Стало так:
C#
1
2
3
4
public interface ISomething
{
   Task<SomeClass> SomeMethodAsync(SomeType someParam);
}
Добавлено через 2 минуты
Цитата Сообщение от megabax Посмотреть сообщение
вот такое вот дело вполне прокатывает
Так лучше не делать, если это не обработчик события. Да и с тем лучше быть осторожным.
С async void можно тех еще веселостей набраться.
0
19 / 19 / 14
Регистрация: 06.08.2009
Сообщений: 533
25.06.2018, 13:11  [ТС] 5
Цитата Сообщение от kolorotur Посмотреть сообщение
Если имеется возможность, я бы изменил сигнатуры своих интерфейсов на асинхронные — наиболее приемлемый и наименее костыльный вариант.
А если потом придется конектиться другой бирже, у которой библиотеки НЕ асинхронные?
А сам эмулятор, его тоже получается надо асинхронным делать, асинхронным?
Тут вот еще мысля возникла, может создать какого-нибудь "демона" (поток о есть) который будет обслуживать торговлю основного алгоритма, принимать от него заявки и посылать их асинхронно на биржу?
0
Эксперт .NET
17688 / 12873 / 3366
Регистрация: 17.09.2011
Сообщений: 21,138
25.06.2018, 13:15 6
Цитата Сообщение от megabax Посмотреть сообщение
А если потом придется конектиться другой бирже, у которой библиотеки НЕ асинхронные?
Намного проще обернуть синхронный метод в асинхронный через Task.Run, чем наоборот.
1
19 / 19 / 14
Регистрация: 06.08.2009
Сообщений: 533
25.06.2018, 13:27  [ТС] 7
Интересно, а если сделать асинхронный метод, но в нем не делать await - ов, он будет работать как синхронный?
0
Эксперт .NET
17688 / 12873 / 3366
Регистрация: 17.09.2011
Сообщений: 21,138
25.06.2018, 13:29 8
Цитата Сообщение от megabax Посмотреть сообщение
а если сделать асинхронный метод, но в нем не делать await - ов, он будет работать как синхронный?
Да, вам даже компилятор подчеркнет этот метод и выдаст предупреждение.
В общем-то, и асинхронный метод с await может отработать синхронно если выполнение не требует блокирования потока.
0
19 / 19 / 14
Регистрация: 06.08.2009
Сообщений: 533
25.06.2018, 14:06  [ТС] 9
И еще какая-то странность.
я сделал так:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
        private async void button1_Click(object sender, EventArgs e)
        {
            listBox1.Items.Add("До");
            proba();
            listBox1.Items.Add("После");
        }
 
        private async void proba()
        {
            i++;
            await proba1();
        }
 
        private async Task<int> proba1()
        {
            MessageBox.Show("proba1");
            return await Task.Run(() => 1);
        }
По логике вещей у меня в ListBox-е сразу должно отобразиться "до" и "после" и возникнуть окно сообщения. Но у меня сначала возникает "до", потом выскакивает окно и после уже появятся надпись "после". Почему так?
Я чего-то видать не понял в асинхронных методах....

Добавлено через 14 минут
Или асинхронные методы только в потоке работают?
0
Эксперт .NET
17688 / 12873 / 3366
Регистрация: 17.09.2011
Сообщений: 21,138
25.06.2018, 14:16 10
Цитата Сообщение от megabax Посмотреть сообщение
Почему так?
Потому что асинхронный код у вас начинается после вывода окна сообщения.
Все до этого происходит синхронно.

Цитата Сообщение от megabax Посмотреть сообщение
Или асинхронные методы только в потоке работают?
Любой код работает в потоке.
Вопрос — в каком
0
19 / 19 / 14
Регистрация: 06.08.2009
Сообщений: 533
25.06.2018, 14:18  [ТС] 11
Сделал так:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
        void func()
        {
            this.Invoke((MethodInvoker)delegate        
            {
                listBox1.Items.Add("До");
            }
            );            
            proba();
            this.Invoke((MethodInvoker)delegate
            {
                listBox1.Items.Add("После");
            });
            
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            Thread myThread = new Thread(func); //Создаем новый объект потока (Thread)
            myThread.Start(); //запускаем поток
        }
 
        private async void proba()
        {
            i++;
            await proba1();
            //MessageBox.Show("proba1");
        }
 
        private async Task<int> proba1()
        {
            MessageBox.Show("proba1");
            return await Task.Run(() => 1);
        }
почему то ничего не изменилось....
0
Эксперт .NET
17688 / 12873 / 3366
Регистрация: 17.09.2011
Сообщений: 21,138
25.06.2018, 14:20 12
Цитата Сообщение от megabax Посмотреть сообщение
почему то ничего не изменилось
По той же причине: вывод сообщения у вас происходит до выполнения асинхронного кода.
0
19 / 19 / 14
Регистрация: 06.08.2009
Сообщений: 533
25.06.2018, 14:25  [ТС] 13
Но ведь proba1 вызывается через await, а сообщение вызывается в нем, в методе proba1
Все равно, как то недопонимаю немного, почему так так...
0
Эксперт .NET
17688 / 12873 / 3366
Регистрация: 17.09.2011
Сообщений: 21,138
25.06.2018, 14:39 14
Цитата Сообщение от megabax Посмотреть сообщение
Но ведь proba1 вызывается через await, а сообщение вызывается в нем, в методе proba1
Наличие await и async в коде не делают метод асинхронным. Асинхронным его делает асинхронная реализация самого метода
async и await используются для управлениея уже имеющейся асинхронностью.

Цитата Сообщение от megabax Посмотреть сообщение
Все равно, как то недопонимаю немного, почему так так...
Перед тем, как выполняется асинхронное ожидание (await), сначала выполняется метод proba, который возвращает Task.
await применяется к возвращаемому Task, а не к самому методу.
Само слово await разворачивается компилятором в код, который проверяет статус полученного таска: если работа уже завершена, то извлекается результат (если он есть) и метод продолжает работу дальше. Если же задача находится в процессе, то остаток метода, который идет после await выносится в отдельный метод и подписывается как продолжение полученной задачи, после чего текущий метод завершает работу, возвращая задачу, подписанную на продолжение. Ну или ничего не возвращает, если это async void.

Зная это, довольно несложно проследить что происходит в вашем коде:
1. button1_Click: В список добавляется "До".
2. button1_Click: Вызывается метод proba
3. proba: Производится инкремент i
4. proba: Вызывается метод proba1
5. proba1: Показывается сообщение
6. proba1: Вызывается метод Task.Run, который возвращает Task
7. proba1: делается проверка: завершен ли полученный Task.
8. proba1: Если да, то из него извлекается результат и метод proba1 возвращает завершенную задачу с этим результатом.
9. proba1: Если нет, то шаг 8 выносится в отдельный метод, который подписывается на продолжение задачи Task и возвращается задача, представляющая эту "цепочку".
10. proba: Проверяется Task, который вернул метод proba1.
11. proba: Если полученный Task уже завершен (если отработал шаг 8), то метод завершает работу.
12. proba: Если полученный Task еще не завершен (если отработал шаг 9), то метод подписывает остальные инструкции на продолжение этой задачи и завершает работу. В вашем случае продолжения нет, потому метод просто завершает работу.
13. button1_Click: В список добавляется "После".

Как видите, у вас сообщение выводится еще до того, как начинает выполняться асинхронный код.
0
19 / 19 / 14
Регистрация: 06.08.2009
Сообщений: 533
25.06.2018, 16:07  [ТС] 15
Думал понял... но
Делаю вот так:
C#
1
private Task<double> res;
потом:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
        private async Task<double> proba2()
        {
            double a = 0;
            listBox2.Items.Add("eee" + DateTime.Now.ToString());
            for (long i = 1; i < 1000000000; i++) a++;
            listBox2.Items.Add("ddd" + DateTime.Now.ToString());
            return await Task.Run(() => a);
        }
 
        private async void button3_Click(object sender, EventArgs e)
        {
            listBox2.Items.Add("До" + DateTime.Now.ToString());
            res=proba2();
            string s=res.Status.ToString();
            listBox2.Items.Add("После " + DateTime.Now.ToString()+"   "+s);
        }
 
        private void button4_Click(object sender, EventArgs e)
        {
            if(res!=null)
            {
                listBox2.Items.Add(res.Status);
                if (res.IsCompleted) MessageBox.Show(res.GetAwaiter().GetResult().ToString());
            }
        }
И что получается? Нажимаю на кнопку 3, у меня зависает на некоторое время, на кнопку 4 не могу нажать. Затем появляться данные в лист боке, которые указывают, что "после" добавилось когда отработал цикл (на несколько сек. позже). И только после этого начинает нажиматься копка 4 выдавать результат выполнения proba2()
0
Эксперт .NET
17688 / 12873 / 3366
Регистрация: 17.09.2011
Сообщений: 21,138
25.06.2018, 16:10 16
Лучший ответ Сообщение было отмечено megabax как решение

Решение

megabax, все то же самое: единственный участок с асинхронным кодом у вас — это вызов Task.Run, который отрабатывает очень быстро, т.к. ничего не делает.
Весь остальной код выполняется синхронно.

Попробуйте так и сравните:
C#
1
2
3
4
5
6
7
8
9
10
11
12
        private async Task<double> proba2()
        {
            listBox2.Items.Add("eee" + DateTime.Now.ToString());
            var result = await Task.Run(() => 
            { 
               double a = 0;
               for (long i = 1; i < 1000000000; i++) a++;
               return a;
            });
 
           listBox2.Items.Add("ddd" + DateTime.Now.ToString());
        }
1
19 / 19 / 14
Регистрация: 06.08.2009
Сообщений: 533
26.06.2018, 08:04  [ТС] 17
kolorotur, Спасибо большущее. Теперь что-то получилось.
0
26.06.2018, 08:04
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
26.06.2018, 08:04
Помогаю со студенческими работами здесь

Как правильно работать с Recordset
Здравствуйте. Сейчас данные получаю так: SQLText = &quot;select t.znach from chasspt t where...

Как правильно работать с NRF24L01?
Доброго дня. Почему не сбрасывается прерывание на самом модуле? Как нужно правильно изначально...

Как правильно работать с данными
Добрый день форумчане! Хочу написать программу по типу ежедневника, не веб. Столкнулся с...

Как правильно работать с setlocale
после добавления setlocale не выводятся последние printf,которые после цикла. Почему так?может я...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
17
Ответ Создать тему
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru