Страницы

Поиск по вопросам

Показаны сообщения с ярлыком async-await. Показать все сообщения
Показаны сообщения с ярлыком async-await. Показать все сообщения

суббота, 4 апреля 2020 г.

Задержки при ожидание ответа (Dataflow)

#c_sharp #async_await #tpl_dataflow

                    
В программе необходимо в отдельном потоке проводить некоторые операции, как например
отправлять запрос серверу и ждать от него ответ. Для реализации использую библиотеку
Dataflow, дабы выполнять это всё последовательно. 
Реализовал такой класс:

partial class PoolManager
{
    CancellationTokenSource CT;
    TransformBlock, string> ActionBlock;

    public PoolManager()
    {
        PM = this;
        CT = new CancellationTokenSource();
        ActionBlock = new TransformBlock, string>(async n =>
        {
            await Task.Delay(2000);
            string a = "";
            try
            {
                a =  n();
            }
            catch (Exception e) { }
            return a;
        },
        new ExecutionDataflowBlockOptions { CancellationToken = CT.Token });
    }

    public void Add(Action func, string message)
    {
        ActionBlock.Post(new Func(delegate () { func(); return message; }));
    }
}  


И целом работает как нужно, но когда я посылаю в него функцию, внутри которой есть
отправка и ожидание ответа, то на моменте ожидания, интерфейс программы подвисает.
В среднем это 100мс, иногда доходит и до 500мс или 1000мс, что становится очень заметно.
Не понимаю в чём причина, ибо он в отдельном потоке должен идти.
    


Ответы

Ответ 1



Вопрос решился сам собой. В целом данный код работает вполне корректно, и проблема таилась глубже. В конкретной задаче на которой я тестировал этот код, в TransformBlock отправлялась функция находящаяся в элементе управления UserControl. В этой функции вызывался метод экземпляра класса, и этот метод посылал запрос на сервер и ожидал ответа. Метод был синхронный и потому поток в котором он вызывался должен был блокироваться. Для того, что бы этого не было делал это в паралельном потоке, но основной поток всё равно блокировался. В итоге я обратил внимание, что код в посылаемой функции выполяется через Dispatcher, дабы я мог выполнять заполнение ProgressBar, и следовательно проверив номер потока через Thread.CurrentThread.ManagedThreadId, при входе в функцию и в диспетчер, понял что код внутри Dispatcher выполняется в уже в потоке UI, а не в потоке TransformBlock, от того запрос и проходил в потоке UI, что и вызывало подвисание. Спасибо VladD, за помощь в решении данной проблемы.

понедельник, 30 марта 2020 г.

Делать ли каскад методов асинхронными, если они могут возвращать просто Task<T>

#c_sharp #net #async_await #async #clr


Терзает вопрос, найти внятный ответ не могу. Гуглится не то.

Вот у меня есть действительно асинхронный метод, в котором несколько await'ов. Этот
метод вызывается на более верхнем уровне:

public async Task UpperLevelMethod()
{
    return await _component.RealyAsyncMethod();
}


Ну и так далее, по всем слоям приложения. 

Но ведь по идее, можно написать так:

public Task UpperLevelMethod()
{
    return _component.RealyAsyncMethod();
}


И вызов await UpperLevelMethod() будет корректно отрабатывать. И, на сколько я понимаю,
этот метод даже лучше, т.к. не будет создаваться дополнительные экземпляры IAsyncStateMachine,
поправьте, если я не прав.

Интересно, что авторы статей и книг в красках описывают последствия упаковки/распаковки,
но этот момент как-то упускается в разделе про асинхронность.

Есть ли какие-то подводные камни, у такого вызова? Либо, в случаях, когда не нужно
делать асинхронное продолжение, можно (а возможно даже лучше) делать метод не асинхронным?

P.S. Тот же Скит описывает, что для проверки аргументов лучше делать так:

public Task UpperLevelMethod(string arg)
{
    /*
    Проверка аргумента и выброс исключения в случае ошибки
    */
    return _component.RealyAsyncMethod();
}


Но он говорит именно про проверку аргументов, чтобы она проходила в синхронном режиме.
Об оптимизации - ничего.

Update: 
Консольное приложение, собраное в релизе, с таким кодом:

static void Main(string[] args)
{
    Task.Run(Foo5);
}

static async Task Foo1() => await Task.FromResult(5);

static Task Foo2() => Foo1();

static Task Foo3() => Foo2();

static Task Foo4() => Foo3();

static Task Foo5() => Foo4();


весит 8 192 байт, при добавлении await'ов - 10 240 байт.
    


Ответы

Ответ 1



Совершенно верно, лучше не писать лишний раз async если код работает и без него. Нет никаких причин писать лишние слова async и await.

вторник, 17 марта 2020 г.

Sockets client+server with await/async c# 5.0

#c_sharp #async_programming #async_await


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

Проблема такова. Хочу написать простое асинхронное клиент-серверное приложение с
банальной передачей байт. Удалось найти и реализовать 2 решения: колбэки и await/async
базирующиеся на tcplistener и networkStream. Но это все не то. Пытаюсь написать базируясь
на Task, Sockets и async/await без tcplistener и networkStream. Почитал некоторую литературу,
но необходимого ответа/примера не нашел. 

Знаю, что делается это базируясь на следующем:

public static Task ConnectAsync(this Socket socket, EndPoint remoteEP) {
    return Task.Factory.FromAsync(socket.BeginConnect, socket.EndConnect, remoteEP,
null);
}


но пока ничего не выходит. Даже сервер не могу "собрать" воедино. Прошу помощи.

Обновление

Удалось подключиться.А вот с передачей пока не получается.

private readonly Socket _server;
public ServerSocket(IPAddress ipAddress, int port) {
    _server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    _server.Bind(new IPEndPoint(ipAddress, port));
    _server.Listen(20);
    Console.WriteLine("Wait connection");
    Accept();
}
private async void Accept() {
    for (;;) {
        var socket = await Task.Factory.FromAsync < Socket > (_server.BeginAccept,
_server.EndAccept, true);
        Console.WriteLine("Connected");
    }
}

    


Ответы

Ответ 1



Для начала, правильный путь — это именно async/await. По поводу работы с сокетами, вот есть хороший обзор. Итак, создаём серверный сокет: public ServerSocket(IPAddress ipAddress, int port) { _server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); _server.Bind(new IPEndPoint(ipAddress, port)); _server.Listen(20); } Здесь мы создали сокет, но основная работа у нас не мгновенна, поэтому выносим её в метод Run. Task AcceptAsync() { return Task.Factory.FromAsync( _server.BeginAccept, _server.EndAccept, null); } public async Task Run() { while (true) { var client = await AcceptAsync(); var clientServeTask = ServeClient(client); // что дальше делать с этим заданием - ваше дело // например, вы можете просто записать его в список } } ConnectAsync для сервера не нужен. В ServeClient инстанциируеем объект, представляющий клиента: async Task ServeClient(Socket client) { using (var c = new Client(client)) await c.RunAsync(); } В объект Client лежит собственно логика вашего сервера. byte[] greeting = Encoding.UTF8.GetBytes("Hi, I'm server ru.SO/2015"); public async Task RunAsync() { await SendAsync(greeting); var result = await ReceiveAsync(20); // ... } (Возможно, вам захочется вернуть результат.) Ну и вспомогательные процедуры: Task TrySendAsync(byte[] buffer, int offset, int size, SocketFlags flags) { return Task.Factory.FromAsync( _clientSocket.BeginSend, _clientSocket.EndSend, buffer, offset, size, flags, null); } Task SendAsync(byte[] buffer) { int sent = 0; int remaining = buffer.Length; while (remaining > 0) { var partSent = await TrySendAsync(data, sent, remaining, SocketFlags.None); sent += partSent; remaining -= partSent; } } Не забудьте закрыть клиентский сокет в Dispose. Да, наверняка вам захочется суметь оборвать работу сервера. Для этого вам стоит протянуть CancellationToken'ы через Task'и.

Ответ 2



Могу посоветовать пример из другого вопроса - https://ru.stackoverflow.com/a/516728/192004 . Там создается 10 клиентов, сервер поочередно принимает их и отправляет ответы. Сервер работает в одном потоке (точнее, в одной Task), клиенты могут работать в разных потоках. Всё на async/await + куча советов.

воскресенье, 8 марта 2020 г.

Использовать await как промис для пользовательского действия

#c_sharp #net #vbnet #async_await


Хочу использовать Task как промис. Т. е. вызываемая функция создаёт некий Task, а
вызывающая применяет к нему await. Тот таск ничего не делает и просто ждёт, пока кто-то
ещё его заресолвит. Примерно как с костылём в следующем коде:

Imports System.Threading

Public Class Form1
  Private Async Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    Me.Text = "Click the button to continue"
    Await ClikTheButton()
    Me.Text = "Thanks for clicking"
  End Sub

  Private Function ClikTheButton() As Task
    Dim Sem As New SemaphoreSlim(0, 1)

    Dim Handler As EventHandler = Sub(sender As Object, e As EventArgs)
                                    Sem.Release()
                                    RemoveHandler Button1.Click, Handler
                                  End Sub

    AddHandler Button1.Click, Handler
    Return Sem.WaitAsync()
  End Function
End Class

    


Ответы

Ответ 1



Надо использовать TaskCompletionSource: Task ClickTheButton(CancellationToken token) { var tcs = new TaskCompletionSource(); var registration = token.Register(() => tcs.TrySetCanceled()); EventHandler handler = (o, e) => tcs.TrySetResult(null); button1.Click += handler; tcs.Task.ContinueWith(_ => { button1.Click -= handler; registration.Dispose(); }, TaskScheduler.FromCurrentSynchronizationContext()); return tcs.Task; } Вот альтернативный вариант того же кода: async Task ClickTheButton(CancellationToken token) { var tcs = new TaskCompletionSource(); EventHandler handler = (o, e) => tcs.TrySetResult(null); using (token.Register(() => tcs.TrySetCanceled()) try { button1.Click += handler; await tcs.Task; } finally { button1.Click -= handler; } }

Синхронный и асинхронный методы и дублирование кода

#c_sharp #async_await #рефакторинг


Здравствуйте. У меня есть два очень похожих метода, один обычный а другой асинхронный.
Выглядят они так: 

public async Task> GetDataAsync()
{            
    var types = _listCache.Get(CacheKey);
    if (types == null)
    {
        types = await GetDataAsync(_settings.Url, Converter);
        _listCache.Add(CacheKey, types);
    }
    return types;
}

public List GetData()
{            
    var types = _listCache.Get(CacheKey);
    if (types == null)
    {
        types = GetData(_settings.Url, Converter);
        _listCache.Add(CacheKey, types);
    }
    return types;
}


Можно заметить что методы почти одинаковы и код в них дублируется чего хотелось бы
избежать, Можно ли как-то сделать это безболезненно? Будут ли например дедлоки если
синхронную версию написать таким образом: 

public List GetData()
{                                
    return GetDataAsync().Result;
}

    


Ответы

Ответ 1



Да, дедлоки будут. Допустим, вы вызываете в UI-потоке GetDataAsync().Result. При этом код выполняется синхронно до момента await GetDataAsync(_settings.Url, Converter);, и далее возвращается в синхронное ожидание выдачи результата Result. Что происходит, когда отрабатывает await? Код пытается доставить continuation в основной поток, но поток заблокирован до получения результата. Вот вам и дедлок. Не пытайтесь предоставить API «на все случаи жизни». Если функция по своей сути асинхронная, предоставляйте только асинхронный интерфейс. Если функция по своей сути синхронная (например, она не ожидает внешних событий, наподобие чтения файла или прихода информации по сети), предоставляйте только синхронный интерфейс. Если синхронный клиент пользуется по сути асинхронным интерфейсом, он по-хорошему должен стать сам асинхронным. Ну или если хочется костыль, то выгрузите в фоновый поток: Task.Run(<асинхронная функция>).Result. Ещё по теме: https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/

Ответ 2



У подобного решения три проблемы. Программа повиснет, если такой код будет выполняться в потоке UI или любом другом однопоточном контексте. Подробнее о причинах и как бороться - тут: Зависает оператор `await` в оконном приложении / программа висит при вызове Task.Result или Wait Вылетевшее исключение обернется в AggregateException, что сделает некрасивым стек вызовов, и затруднит классификацию исключений в блоках catch. Вызываемые методы GetDataAsync и GetData могут работать по-разному.

суббота, 7 марта 2020 г.

await Task не содержит определение Delay

#c_sharp #async_await


using System.Threading.Tasks;

private async Task PageLoad(int TimeOut)
{
    TaskCompletionSource PageLoaded = null;
    PageLoaded = new TaskCompletionSource();
    int TimeElapsed = 0;
    _webBrowser.DocumentCompleted += (s, e) =>
    {
        if (_webBrowser.ReadyState != WebBrowserReadyState.Complete) return;
        if (PageLoaded.Task.IsCompleted) return; PageLoaded.SetResult(true);
    };

    while (PageLoaded.Task.Status != TaskStatus.RanToCompletion)
    {
        TimeElapsed++;
        if (TimeElapsed >= TimeOut * 100) PageLoaded.TrySetResult(true);
        await Task.Delay(10);
    }
}


Почему VS пишет что Task не содержит определение для Dеlay? Вроде для ее работы нужна
только System.Threading.Tasks или опять что-то поменялось?
    


Ответы

Ответ 1



Task.Delay доступен с версии .NET Framework 4.5. Есть аналог для .NET Framework 4 в реализации Microsoft Async использовать так: await TaskEx.Delay(1000);

Почему исключения в асинхронном коде считаются необработанными?

#c_sharp #исключения #async_await


Есть код скачивания файлов, на нижнем уровне есть простой цикл с ограничением, чтобы
сделать несколько попыток скачивания, выглядит упрощенно вот так:

private static bool Failed = false;

static void Main(string[] args)
{
  TaskScheduler.UnobservedTaskException += (o, a) => { throw a.Exception; };
  ChapterDownload().Wait();
}

public static async Task ChapterDownload()
{
  try
  {
    var pages = Enumerable.Range(0, 20);
    var pTasks = pages.Select(page =>
    {
      return PageDownload()
      .ContinueWith(t => Console.WriteLine("{0}{1}", t.Status, t.IsFaulted));
    });
    await Task.WhenAll(pTasks.ToArray());
  }
  catch (Exception ex)
  {
    //
  }
}

public static async Task PageDownload()
{
  if (Failed)
    throw new Exception("boom");

  try
  {
    // var file = await new WebClient().DownloadDataTaskAsync(new Uri(@"http://example.com"));
    var file = await DownloadFile(new Uri(@"http://example.com"));
    if (file != null)
      throw new Exception("Restart download, downloaded file is corrupted");
  }
  catch (Exception ex)
  {
    Failed = true;
    await PageDownload();
  }
}

public static async Task DownloadFile(Uri uri)
{
  byte[] result;
  WebResponse response;
  var request = WebRequest.Create(uri);

  try
  {
    response = await request.GetResponseAsync();
    using (var memory = new MemoryStream())
    {
      await response.GetResponseStream().CopyToAsync(memory);
      result = memory.ToArray();
    }
  }
  catch (System.Exception ex)
  {
    return null;
  }
  if (response.ContentLength == result.LongLength)
    return result;
  return null;
}


Проблема в чём - в текущем виде, происходит UnobservedTaskException, с текстовкой
boom, т.е. явно моё. При этом, стоит мне заменить скачивание с самопала на webclient
- UnobservedTaskException больше не возникает. В чём разница?
    


Ответы

Ответ 1



Чтобы исключения задачи (Task) считались обработанными и не вызывали событие TaskScheduler.UnobservedTaskException необходимо их перевыбросить, например t.Result, t.Wait() или t.GetAwaiter().GetResult(), или просмотреть, обратившись с свойству Exception: t.Exception. В Вашем коде проблема возникает потому, что вы ждёте завершение не самих исходных задач, а их продолжений. В то время как продолжения не делают ничего, чтобы обработать исключения исходных задач. В результате исключения, возникшие в исходных задачах, остаются необработанными.

пятница, 14 февраля 2020 г.

Выполнение синхронного кода в async методе

#c_sharp #async_await


Есть у меня promise метод чтения данных из моего потока:

public void ReadAsync(AsyncReadCallback callback, AsyncException exception
= null) {
    try {
        Thread thread = new Thread(new ThreadStart(() => {
            try {
                T request = ReadSync();
                callback.Invoke(ref request);
            } catch(Exception e) {
                if(exception == null) {
                    throw e;
                }
                exception.Invoke(e);
            }
        }));
        thread.Start();
    } catch (Exception e) {
        if(exception == null) {
            throw e;
        }
        exception.Invoke(e);
    }
}


Вызывать я его могу, соответственно так:

stream.ReadAsync((string result) => {
    Console.WriteLine("receive: "+result);
},(Exception e) => {
    Console.WriteLine("exception: "+e.Message);
});




Но, в погоне за сахаром, решил попробовать сделать аналогичное поведение через async,
что даст возможность выполнять всё без потоков, более того, заставлять делать await
в нужных мне местах:

public async Task ReadAsync() {
    return ReadSync();
}


Вызывать планирую, примерно так:

Task taskRead = stream.ReadAsync();
...
string data = await taskRead;
Console.WriteLine(data);


Однако, VS2017 говорит, что мой код будет выполняться только синхронно, и что там
нет await. Я не могу понять, возможно ли вообще то что я задумал?

Вот что говорит сама студия (скрин очень неудобный):

Всё что я хочу - выполнять синхронный метод так, как если бы он был асинхронен. (Возможно,
звучит глупо, но, не могу сформулировать по другому)
    


Ответы

Ответ 1



Ключевое слово async позволяет выполнять асинхронное ожидание других задач (делать await), но простое добавление этого слова ничего не дает вам в плане многопоточности. Перемещение выполнения в другой поток все еще остается именно вашей задачей. Выполнить задачу в фоновом потоке можно при помощи Task.Run: public Task ReadAsync() { return Task.Run(() => ReadSync()); } Однако, написание подобных оберток является дурной практикой: от асинхронного кода ожидается что он по крайней мере часть своей работы выполнит не занимая потока. Вероятно, правильным будет не делать отдельного метода ReadAsync - а вставить конструкцию Task.Run(() => ReadSync()) непосредственно в том месте где вам нужно прочитать данные: Task taskRead = Task.Run(() => stream.ReadSync()); ... string data = await taskRead; Console.WriteLine(data); А еще правильнее - переписать метод ReadSync так, чтобы он стал асинхронным.

Task.Run - антипаттерн async/await? C#

#c_sharp #async_await #async #async_programming


Недавно прочитал статью на хабре (upd: из комментов понял, что нужно прицепить цитату,
по которой далее вопрос)


  Как только код доходит до метода Task.Run(), достаётся другой поток из пула потоков
и в нём исполняется код, который мы передали в Task.Run(). Старый поток, как и положено
приличному потоку, возвращается в пул и ждёт, когда его снова позовут делать работу.
Новый поток выполняет переданный код, доходит до синхронной операции, синхронно выполняет
её (ждёт пока операция не будет выполнена) и идёт дальше по коду. Иными словами, операция
так и осталась синхронной: мы, как и раньше, используем поток во время выполнения синхронной
операции. Единственное отличие — мы потратили время на переключение контекста при вызове
Task.Run() и при возврате в ExecuteOperation(). Всё стало немножечко хуже.


Один из вопросов, который там рассматривается: вызов Task.Run - это антипаттерн,
и нужен он только для отзывчивости GUI.

Вопрос именно про Task.Run(() => _anyWork()), где _anyWork() содержит синхронный
код. То, что написано в статье, звучит достаточно логично, если делать так:

await DoWork();
...

Task DoWork() => Task.Run(_work);


Да, в таком случае создается лишняя нагрузка на пул потоков. Но, ведь если делать так:

var task1 = DoWork1();
var task2 = DoWork2();
var task3 = DoWork3();
await Task.WhenAll(task1, task2, task3);


Task.Run сразу превращается в нормальный код, ведь так? 

Поток, который будет выполнять этот код, создаст три других потока (upd: оговорился:
инициирует добавление работы в очередь, которая будет запущена в ThreadPool), которые
параллельно будут выполнять свою работу параллельно. Дальше, когда он встретит await
- он вернет управление (в итоге, скорее всего, вернется в пул). Исправьте, пожалуйста,
если не так.

Если это так - возникает вопрос: где проходит эта грань, между плохой реализацией,
и нормальной? В небиблиотечном коде понятно - если вызывается метод, а ожидание где-то
дальше - то можно делать Task.Run. В библиотечном же - с одной стороны, мы можем распараллелить
работу своих методов, если клиент будет ожидать их после вызова. С другой - мы можем
зря увеличить нагрузку, если клиент будет ожидать результат сразу при вызове. Знать
точно, как будет вызывать методы клиент - мы не можем, можем только дать рекомендации
в документации.

Возможно есть какие-то официальные рекомендации MS? На msdn я нашел только сухое
описание работы методов.
    


Ответы

Ответ 1



Один из вопросов, который там рассматривается: вызов Task.Run - это антипаттерн, и нужен он только для отзывчивости GUI. Task.Run Method Ставит в очередь заданную работу для запуска в ThreadPool и возвращает задачу или дескриптор Task для этой работы. То есть Task.Run - это способ выполнить какую-то работу в пуле потоков с возможностью ожидания результата асинхронно. Когда это может понадобится? Возможные примеры использования: 1) Запуск IO/CPU нагрузки пуле потоков, чтобы не грузить основной UI поток (пример) 2) Запуск асинхронного кода синхронно из UI потока. Такое может потребоваться, когда у вас большое приложение и вы постепенно переходите на асинхронные вызовы вместо синхронных, но не везде пока можете вызывать ваш новый API асинхронно, пр этом вам надо избегать дедлоков в UI потоке, например Task.Run(()=>CallSmthgAsync().GetAwaiter().GetResult()).GetAwaiter().GetResult(); - это не оч красивый код и он должен быть исправлен, но не всегода можно внедрить асинхронные вызовы за один заход, потому я такое сам делал и видел иногда. Когда это НЕ надо: когда вас не заботит факт блокировки текущего потока какой то синхронной работой. В прмере в вопросе задача и так уже запущена в пуле потоков, потому переключаться в другой поток и там выполнять что то синхронное смысла не имеет. Но если бы та же работа была запущена из UI потока, то без Task.Run все UI приложение бы встало колом. Что касается библиотечного кода. Стройте ваш API и его реализацию исходя из требований и вариантов использования. Если у вас много I/O операций (работа с файлами, с сетью, с БД), то это по сути самой собой намекает на необходимость асинхронного API. Если вас смущает выбор между синхронным API и асинхронным, то реализуйте оба, пусть клиент выбирает,что ему нужно. Отсюда вывод: самое главное, чтобы не наломать дров, всегда знайте что вы делаете и зачем вы это делаете. Переключайте контекст тогда, когда у вас есть причина это делать. Если каждая строка вашего кода будет обоснована и иметь причину, почему она именно так написана, то проблем с выбором хороший код/ плохой код у вас будет гораздо меньше.

Ответ 2



Код метода *Async выполняется в текущем потоке до первого await. И текущий поток будет захвачен. Например, HttpClient до начала асинхронного запроса синхронно запрашивает dns, что может быть долгой операцией. И вы можете решить, что вас больше беспокоит - ожидание текущего потока или накладные расходы от Task.Run() (которые минимальны по факту, разве что раздувают код)

Ответ 3



использовать Task: если внутри нужно использовать асинхронную операцию: внутри их несколько синхронный код и асинхронная операция вот интересная статья о using(IDisposable) и потенциальном баге если ты делаешь свою собственную асинхронную операцию (например превращаешь событие в Task) - часто используют TaskCompletionSource второе похоже на I/O-bound операцию - не уверен, писать ли отдельным пунктом

воскресенье, 2 февраля 2020 г.

Как работать с gui из Async потока? [дубликат]

#c_sharp #многопоточность #асинхронность #async_await


        
             
                
                    
                        
                            На этот вопрос уже даны ответы здесь:
                            
                        
                    
                
                        
                            Работа с контролами из фонового потока
                                
                                    (2 ответа)
                                
                        
                                Закрыт 3 года назад.
            
                    
Как работать с gui из Async? Ну вот например : есть progressbar и есть button у которого
на событии Async onclick лежит следующий код Await Task.Run((){progressbar.value =
77}) и он конечно же "вылетает", так вот, что мне делать?
    


Ответы

Ответ 1



Самый простой вариант — если вы уж находитесь в async-функции, то не вызывайте Task.Run раньше времени. Например, вы можете сделать как-то так: async void OnClick(object sender, RoutedEventArgs e) { button.IsEnabled = false; for (int i = 0; i < 10; i++) { // выгружаем в фоновый поток ту операцию, которая должна быть асинхронной bool isGood = await Task.Run(() => ComputePart(i)); // остаток кода продолжает бежать в UI-потоке if (isGood) progressBar.Value = i; } button.IsEnabled = true; } Ещё три варианта описаны в отличном ответе @Lightness.

Ответ 2



Как вариант можно использовать SynchronizationContext. Пример: private async void ChangeProgressBar() { var uiSync = SynchronizationContext.Current; await Task.Run(() => { uiSync.Send(state => { progressbar.value = 77 }, null); }); } Также можно использовать Progress< T>. Пример: private async void ChangeProgressBar() { IProgress progress = new Progress(value => { progressBar.Value = value; }); await Task.Run(async () => { for (int i = 0; i < 100; i++) { progress.Report(i); await Task.Delay(20); } }); } Еще один вариант это использование Dispatcher. Пример: private async void ChangeProgressBar() { await Task.Run(() => { progressBar.Dispatcher.Invoke(() => progressBar.Value = 77, DispatcherPriority.Background); }); }

Не перехватывается выброшенное исключение

#c_sharp #net #async_await


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

    private async void FillCurrentWeather(string place)
    {          
        XmlSerializer serializer = new XmlSerializer(typeof(Current));
        string xml = await GetWeather(CurrentWeatherUrl,place,7);
        if (xml != null)
        {
            try
            {
                TextReader reader = new StringReader(xml);
                Current forecast = (Current)serializer.Deserialize(reader);
                PlaceName.Content = forecast.City.Name + "," + forecast.City.Country;
                CurrentWeather.Update(forecast);
                reader.Dispose();
            }
            catch (InvalidOperationException ex)
            {
                throw new InvalidOperationException(ex.Message, ex.InnerException);
            }
        }
    }


Внешний код

try
{
    FillCurrentWeather(place);
    FillDayWeather(place);
    FillWeekWeather(place);
}
catch (InvalidOperationException ex)
{
    MessageBox.Show("Ошибка при обработке данных внешнего сервиса\n" + ex.Message);
}


Но исключение не обрабатываеися и выскакивает в отладчике. В чем может быть причина?
    


Ответы

Ответ 1



Во время выполнения под отладчиком выпадают все исключения, отмеченные в списке Debug -> Windows -> Exception Settings (Ctrl+Alt+E). Это нормально. Если вы продолжите выполнение программы (F5), то дальше отработают ваши catch блоки. UPD С добавление полного кода стала понятна настоящая причина, к которой предыдущий абзац не имеет никакого отношения. Проблема в этом: async void FillCurrentWeather Для полного понимания того, как работает async/await, отсылаю к видео. Здесь же вкратце опишу, что происходит при выполнении программы. Итак: В блоке try внешнего кода вызывается метод FillCurrentWeather(place);. Выполнение метода FillCurrentWeather доходит до этой строки и покидает его*: string xml = await GetWeather(CurrentWeatherUrl,place,7); В блоке try внешнего кода отрабатывают все остальные методы. Метод GetWeather завершает выполнение, продолжается выполнение метода FillCurrentWeather, вываливается исключение. НО! Блок try/catch во внешнем коде уже завершился, он не может поймать это исключение! Поэтому исключение вываливается на самом верхнем уровне как необработанное (UnhandledTaskException). async void опасны именно по этим причинам: мы не знаем, когда они на самом деле завершаются соответственно мы не может адекватно реагировать на исключения в таких методах async void сигнатуру можно использовать только для событий и методов, имеющих событийную семантику, плюс не забывать целиком оборачивать все тело таких методов в try/catch, поскольку если возникнет исключение, вы либо получите необработанное исключение (если возникло в UI потоке), либо, в случае если оно возникло в потоке пула, вы об этом или не узнаете, или это исключение убьет процесс (в случае с .NET 4.0 будет именно так). Какие у вас варианты решения? В данном случае -- только один: изменить сигнатуру метода на async Task и ожидать завершения этого метода во внешнем коде. И еще маленький совет: в вашем случае блок try/catch в методе FillCurrentWeather вообще не нужен, поэтому что он по сути ничего не делает. Просто уберите его. (А впредь, если хотите пробрасывать исключения, оборачивая их в другие, всегда включайте исключение целиком, а не его InnerException, которого может и не быть: throw new Exception("This is new exception", e)). *на самом деле это упрощение, и выполнение продолжится вглубь до того момента, когда вызов уйдет к IO-устройству. Важно лишь, что программа не дожидается завершения этого метода. UPD2 Чтобы не терять параллелизацию получения данных о погоде, которая невольно достигалась при использовании async void методов, нужно использовать Task.WhenAll: try { var currentTask = FillCurrentWeather(place); var dayTask = FillDayWeather(place); var weekTask = FillWeekWeather(place); await Task.WhenAll(currentTask, dayTask, weekTask); } catch (InvalidOperationException ex) { MessageBox.Show("Ошибка при обработке данных внешнего сервиса\n" + ex.Message); }

среда, 29 января 2020 г.

Асинхронный map [дубликат]

#nodejs #async_await #backend


        
             
                
                    
                        
                            На этот вопрос уже даны ответы здесь:
                            
                        
                    
                
                        
                            Как выполнить последовательно несколько асинхронных фунций
в Javascript без коллбэков?
                                
                                    (3 ответа)
                                
                        
                                Закрыт 1 год назад.
            
                    
Пишу бекенд на node (v 8.11.4)

Сервер общается с vk api. У vk есть ограничение - 5 запросов с секунду.

Чтобы не уперется в ограничение было решено сделать функцию sleep:

export default (ms) => {
    return new Promise(resolve => setTimeout(resolve, ms));
};


Далее есть мой метод (туда импортирую функцию sleep):

app.post(path, (req, res) => {
    [1,2,3].map(async item => {
        await sleep(1000);
        console.log(item);
    })
})


Здесь я ожидаю что console.log будет срабатывать раз в секунду. Но этого не происходит.
Проходит 1 секунда и console.log выводится 3 раза.
    


Ответы

Ответ 1



Все дело в том что этот код: [1,2,3].map(async item => { await sleep(1000); console.log(item); }) Тоже самое что и этот let foo = async item => { await sleep(1000); console.log(item); } foo(); foo(); foo(); Все три функции вызываются сразу. Каждая ждет по секунде и затем одна за другой выводят item в консоль. Так как начинают ждать они одновременно, то и заканчивают тоже, поэтому вы видите что сообщения выводятся сразу все. Можно сделать так: const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]; const foo = async (item, i) => { setTimeout(() => { return console.log(item); }, 1000 * i); // 1000 - это ваша задержка между вызовами } numbers.map(foo); Можно ставить устанавливать задержку после каждого вызова: const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]; const delayLoop = (array, delay) => { let i = 0; const loop = () => { setTimeout(() => { console.log(numbers[i]); if (++i < numbers.length) { loop(); } }, delay); }; loop(); } delayLoop(numbers, 1000); Можно сделать пример выше более универсальным, добавив callback: const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]; const delayLoop = (array, delay, handler) => { let i = 0; const loop = () => { setTimeout(() => { handler(numbers[i]); if (++i < numbers.length) { loop(); } }, delay); }; loop(); } const count = (value) => { console.log(`counter: ${value}`); } delayLoop(numbers, 1000, count);

Ответ 2



Если я правильно понял инужно выводитьв консоль значения из массива с заданным интервалом, то можно сделать так - const sleep = (ms) => { return new Promise(resolve => setTimeout(resolve, ms)); }; const array = [1, 2, 3]; array.reduce(async(result, current) => result.then(() => sleep(1000).then(() => console.log(current))), Promise.resolve());

Ответ 3



Возможно, такие циклы проще делать с помощью for..of циклов: app.post(path, async (req, res) => { for (const item of [1,2,3]) { await sleep(1000); console.log(item); }) })

пятница, 24 января 2020 г.

Использование ConfigureAwait(false) в консольном приложении

#c_sharp #async_await #асинхронность


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

1) Http запросы.

2) Запросы к базе данных.

3) Отправка команд по сокетам в другое консольное приложение.

Перерыл много статей в интернете, где написано, что использование ConfigureAwait(false)
недопустимо в GUI приложении, т.к. там требуется исходный контекст после выполнения
асинхронной функции. У меня консольное приложение, в котором, как я понял, не важно
в каком контексте продолжит выполняться функция. Правильно ли я думаю? Может ли повсеместное
использование ConfigureAwait(false) в моем приложении обернуться боком? А конкретно:

1) Возможна ли взаимоблокировка (Deadlock).

2) Увеличение нагрузки на систему / уменьшение производительности.

3) Выполнение асинхронных функций не по заданному порядку (вычитал и такое в одной
статье).

4) Или какая-нибудь другая проблема, про которую я не знаю.

Пример кода из программы:

private async Task UpdateUserBalance(int id, decimal amount)
{
    Transaction transaction = await DataBase.StartTransactionAsync().ConfigureAwait(false);

    try
    {
        UserModel user = await UserModel.FindAsync(id, transaction).ConfigureAwait(false);

        user.balance += amount;

        await user.Update(transaction).ConfigureAwait(false);

        await transaction.CommitAsync().ConfigureAwait(false);
    }
    catch (Exception e)
    {
        await transaction.RollbackAsync().ConfigureAwait(false);
    }
    finally
    {
        transaction.Dispose();
    }
}


Есть так же функции async void, которые вызывают недоверие по отношению к deadlock-ам.
Пример:

class TimerClass
{
     private Timer _timer; // using System.Timers;

     public TimerClass()
     {
         _timer = new Timer(5 * 1000);
         _timer.Elapsed += Callback;
         _timer.AutoReset = true;
         _timer.Enabled = true;
         _timer.Start();
     }

     private async void Callback(object state, ElapsedEventArgs e)
     {
         // code

         await SomeAsyncFunc().ConfigureAwait(false);

         // code
     }
}


И еще вопрос. Возможен ли deadlock в этом коде? Т.к используется task.Result.

List tasks = new List();

for(int i = 0; i < someLength; i++)
{
    tasks.Add(SomeTaskFunc());
}

await Task.WhenAll(tasks);

for(int i = 0; i < tasks.Count; i++)
{
    Task task = tasks[i];
    // Дальше идут операции с task.Result
}

    


Ответы

Ответ 1



Формально говоря, для консольного приложения или сервиса повсеместное использование ConfigureAwait(false) действительно не нужно. Поскольку контекст синхронизации все равно отсутствует, то нет нужны в привязке конкретного кода к конкретному контексту (как в случае с UI приложением) -- весь код будет работать в пуле потоков. Однако я бы все равно рекомендовал использовать ConfigureAwait(false) везде, где возможно, по двум причинам: Вы повысите переиспользуемость кода. Если код будет затем перенесен в GUI приложение, он сразу будет работать правильно. Если вам или используемой вами библиотеке понадобится установить свой контекст, ваш код продолжит работать корректно (по крайней мере, не изменит своего поведения). Отвечая на конкретные вопросы: Нет. До тех пор, пока у вас отсутствует контекст синхронизации (либо он есть, но без message loop) и вы не используете блокирующие вызовы типа Result и Wait(), можете забыть о дедлоках. Не будет. Не вижу связи (ссылка на статью помогла бы прояснить вопрос). Что касается async void методов, то главная опасность их заключается в двух вещах, которые с ConfigureAwait(false) никак не связаны: Исключения, возникшие в таких методах, не обрабатываются. Для таймера и консольного приложения это будет означать завершение процесса. Поэтому нужно либо искать полностью асинхронные альтернативы, либо использовать в колбэке глобальный try/catch. Такой метод завершает свое выполнение, грубо говоря, сразу после первого await. Т.о., если сам асинхронный вызов или код, находящийся после асинхронного вызова, занимает значительно время, то вызовы колбэка по таймеру могут накладываться друг на друга. Обойти это можно, если каждый раз выключать таймер в начале метода, и снова включать в конце метода. UPD Возможен ли deadlock в этом коде? Т.к используется task.Result. Блокирующее ожидание таска (через Result или Wait()) само по себе не гарантирует вам дедлок! Все зависит от контекста и от того, что происходит внутри вызываемого метода. Если внутри SomeTaskFunc() нет ничего криминального, то дедлок невозможен хотя бы потому, что к моменту обращения к Result все таски уже завершили свою работу (await Task.WhenAll(tasks)). А еще потому, что у вас консольное приложение и вы пользуетесь одним контекстом. Про блокирующее ожидание и дедлоки почитайте в этом вопросе. P.S. написано, что использование ConfigureAwait(false) недопустимо в GUI приложении, т.к. там требуется исходный контекст после выполнения асинхронной функции Не совсем корректное утверждение. Использовать ConfigureAwait(false) в GUI приложениях нужно везде, где нет необходимости выполнять продолжение в UI потоке. Т.е. опускать ConfigureAwait(false) нужно только в UI коде. Везде дальше по дереву вызовов присутствие ConfigureAwait(false) крайне желательно (читай, обязательно).

среда, 22 января 2020 г.

ES6 синтаксис. Как правильно записать?

#javascript #ecmascript_6 #async_await




const handleFiles = () => {
  return async handle => {
    let result;
    result = 'hello';
    return result;
  };
};
handleFiles().then(result => console.log(result)).catch(console.log(e));




Почему не срабатывает данный скрипт?


  Uncaught TypeError: handleFiles(...).then is not a function

    


Ответы

Ответ 1



Проблема в том, что handleFiles возвращает функцию, а не Promise, у которого есть метод then. Для решению нужно либо вызвать возвращенную функцию, либо возвращать результат вызова: const handleFiles = () => { return async handle => { let result; result = 'hello'; return result; }; }; handleFiles()().then(result => console.log(result)).catch(e => console.log(e)); const handleFiles2 = () => { return (async handle => { let result; result = 'hello'; return result; })(); }; handleFiles2().then(result => console.log(result)).catch(e => console.log(e));

Ответ 2



Вот как будет выглядеть ваша промисифицированная функция const handleFiles = (str) => { return new Promise((resolve, reject) => { if(str){ let result = str; resolve(result); } else { reject('Укажите аргумент'); } }); }; В resolve передаете аргумент для then, а в reject аргумент для catch Реализация: handleFiles('1').then(result => console.log(result)).catch(e => console.log(e));

Ответ 3



let handleFiles = new Promise((resolve, reject) => { let result; result = 'hello'; resolve(result); if (false) { reject('error'); } }); handleFiles.then(result => console.log(result), error => console.log(eror));

пятница, 10 января 2020 г.

await + ConfigureAwait()

#c_sharp #net #async_await


Коллеги, добрый день. Прошу помощи с ConfigureAwait().

Мои знания на таком уровне: это метод, который указывает среде выполнения, что при
возвращении из асинхронного метода (запущенного в другом потоке) не нужно пользоваться
контекстом синхронизации. Т.е. не ждать пока освободится поток, в котором началось
выполнение кода (до await), а продолжать выполнение кода (который после await) в том
же потоке, в каком выполнялся сам асинхронный таск (сам await).
Это повышает производительность, однако стоит использовать с осторожностью в задачах
где присутствуют UI потоки (WPF, ASP.NET). Поправьте пожалуйста, если я не прав.

И дальше возникает второй вопрос. Допустим у меня есть такая цепочка асинхронных вызовов:

    public async Task M1()
    {
        return await M2();
    }

    public async Task M2()
    {
        return await M3();
    }

    public async Task M3()
    {
        return await 
             dbContext.Users
                   .ToListAsync()
                   .ConfigureAwait(false);
    }


В методе M3() происходит асинхронное ожидание. EF создает новый поток и читает из
БД, мы ждем. Все ок.

А что происходит в M2()? Создается ли новый поток для выполнения таска await M3().
Аналогичный вопрос для метода M1(). Стоит ли в этих await-ах использовать ConfigureAwait(false),
либо достаточно того, что мы использовали этот метод в M3()?
    


Ответы

Ответ 1



Смотрите. ConfigureAwait(false) действует напрямую только на await, который ожидает данный Task (точнее, данный tasklike). Это означает, что во внешней функции будет обычным образом захвачен контекст, и выполнение вернётся туда по окончанию await. То, в каком или в каких контекстах пробегает код M1, не играет роли, если M2 бежит в синхронизационном контексте.

Один поток на action [закрыт]

#c_sharp #многопоточность #aspnet #mvc #async_await


        
             
                
                    
                        
                            Закрыт. На этот вопрос невозможно дать объективный ответ.
Ответы на него в данный момент не принимаются.
                            
                        
                    
                
                            
                                
                
                        
                            
                        
                    
                        
                            Хотите улучшить этот вопрос? Переформулируйте вопрос,
чтобы на него можно было дать ответ, основанный на фактах и цитатах, отредактировав его.
                        
                        Закрыт 3 года назад.
                                                                                
           
                
        
Читал на хабре статью про async/await и наткнулся на следующую строчку:


  Несмотря на то, что в ASP.NET нет выделенного UI потока, код в
  action-ах контроллеров не может выполняться более чем одним рабочим
  потоком одновременно.


Исходя из этой строчки у меня возникают вопросы:


Зачем так ограничивать action?
Как это реализовано внутрях? lock в ActionInvoker'е?

    


Ответы

Ответ 1



Вы просто не совсем правильно поняли мысль, котору пытался донести автор статьи. Он не пытался сказать, что один конкретный метод Action не может выполняться в нескольких потоках - ведь достаточно очевидно, что это не так. Если у вас пришел десяток одновременных HTTP-запросов на один и тот же Action - то это Action будет выполняться одновременно в 10 разных потоках. Никаких блокировок там нет. Автор статьи на хабре имел ввиду, что один конкретный запрос - один конкретный вызов Action для обработки конкретного запроса - будет выполняться одним конкретным рабочим потоком, к которому при этом будет привязан HttpContext этого самого запроса.

Туманности и мои пробелы в знаниях с async\await и Task'ами в целом

#c_sharp #async_await #async_programming #task #context


Здравствуйте, я захотел опробовать такую вкусняшку C# как async/await и написал тестовую
программу:

class MySynchronizationContext : SynchronizationContext
{
    public override void Post(SendOrPostCallback d, object state)
    {
        Console.WriteLine("Post");
        base.Post(d, state);
    }
    public override void Send(SendOrPostCallback d, object state)
    {
        Console.WriteLine("Send");
        base.Send(d, state);
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        SynchronizationContext.SetSynchronizationContext(new MySynchronizationContext());
        var browsers = GetBrowsers();
        Console.WriteLine("start");
        Console.WriteLine(browsers.Result);
    }
    static async Task GetBrowsers()
    {
        var res = string.Empty;
        var bd = await GetFromFS();
        res += bd;               // <-тут могла быть операция с UI
        var net = await GetFromNet();
        res += " and " + net;   // <-тут могла быть операция с UI
        var cpu = await Task.Run(() => { Thread.Sleep(200); return "edge"; });
        res += " and " + cpu;   // <-тут могла быть операция с UI
        return res;
    }
    static async Task GetFromFS()
    {
        using (var sr = new StreamReader(@"C:\bd.txt"))
        {
            var res = await sr.ReadToEndAsync();
            // тут могла быть операция с UI
            return res + " and firefox";
        }
    }
    static async Task GetFromNet()
    {
        await Task.Delay(200);
        // тут могла быть операция с UI
        return "chrome";
    }

}


Я ожидал, что после каждого await'а мой контекст будет восстанавливаться (ведь будь
это приложение с UI, то надо же получать доступ к контролам), а значит, что после каждого
await'а в консоль будет выводится "Post", но каково было мое удивление когда вывелся
только 2 раза. Отсюда вопрос:


почему контекст привязывается не всегда?


Теперь к async/await: как я понимаю, начиная с var browsers = GetBrowsers(); вызов
будет проводится по стеку вниз до первого таска (в данном случае sr.ReadToEndAsync();,
а может и ниже), и управление сразу перейдет обратно в метод Main(), не дожидаясь завершения
этого таска; остальная часть async-методов будет выполняться после выполнения данного
таска и так далее, из чего возникает следующий вопрос:


кто ждет тот самый первый таск (затем следующие, когда до них дойдет
очередь после await'а), изменяет его состояние на
RanToCompletion? Думаю, было бы глупо думать, что для ожидания
выделяется поток из пула потоков, да?


Третий вопрос стоит на стыке тасков и пула потоков. Продолжение после await, как
я понимаю, выполняется в пуле потоков (там ему передается контекст), но...


что засовывает часть кода после await в пул потоков и когда (когда
таск завершился или когда создался?)?


Заранее спасибо за прояснение ситуации
    


Ответы

Ответ 1



Дело в том, что выполнение метода Post() в вашем контексте вы делегируете базовому методу. А базовый метод ставит делегат на выполнение в пуле потоков. В случае с UI такого не происходит, потому что, например, WinForms контекст полностью переопределяет методы. Вставим логирование контекста и текущего потока в ваш код: class MySynchronizationContext : SynchronizationContext { public override void Post(SendOrPostCallback d, object state) { Console.WriteLine($"Post, thread id = {Thread.CurrentThread.ManagedThreadId}"); base.Post(d, state); } public override void Send(SendOrPostCallback d, object state) { Console.WriteLine($"Send, thread id: {Thread.CurrentThread.ManagedThreadId}"); base.Send(d, state); } public override string ToString() { return "My"; } } public class Program { public static void Main(string[] args) { SynchronizationContext.SetSynchronizationContext(new MySynchronizationContext()); var browsers = GetBrowsers(); Console.WriteLine("start"); Console.WriteLine(browsers.Result); } static async Task GetBrowsers() { LogCurrentContext("GetBrowsers prologue"); var res = string.Empty; var bd = await GetFromFS(); LogCurrentContext("GetBrowsers after GetFromFS"); res += bd; // <-тут могла быть операция с UI var net = await GetFromNet(); LogCurrentContext("GetBrowsers after GetFromNet"); res += " and " + net; // <-тут могла быть операция с UI var cpu = await Task.Run(() => { Thread.Sleep(200); return "edge"; }); LogCurrentContext("GetBrowsers after Task.Run"); res += " and " + cpu; // <-тут могла быть операция с UI return res; } private static void LogCurrentContext(string message) { Console.WriteLine($"{message}: {(SynchronizationContext.Current?.ToString() ?? "default")} context, thread id = {Thread.CurrentThread.ManagedThreadId}"); } static async Task GetFromFS() { LogCurrentContext("GetFromFS prologue"); using (var sr = new StreamReader(@"D:\GetEventsMarkets.sql")) { var res = await sr.ReadToEndAsync(); LogCurrentContext("GetFromFS after ReadToEndAsync"); // тут могла быть операция с UI return res + " and firefox"; } } static async Task GetFromNet() { LogCurrentContext("GetFromNet prologue"); await Task.Delay(200); // тут могла быть операция с UI LogCurrentContext("GetFromNet after Task.Delay"); return "chrome"; } } Чаще всего я получал подобный результат: GetBrowsers prologue: My context, thread id = 1 GetFromFS prologue: My context, thread id = 1 Post, thread id = 1 GetFromFS after ReadToEndAsync: default context, thread id = 3 start Post, thread id = 3 GetBrowsers after GetFromFS: default context, thread id = 3 GetFromNet prologue: default context, thread id = 3 GetFromNet after Task.Delay: default context, thread id = 4 GetBrowsers after GetFromNet: default context, thread id = 4 GetBrowsers after Task.Run: default context, thread id = 4 Здесь нам интересны две вещи. В кастомном контексте вызываются только прологи методов GetBrowsers() и GetFromFS(). После того, как выполняется строка await sr.ReadToEndAsync(), продолжение метода GetFromFS() постится в кастомный контекст. Это первый вызов Post(). Далее метод GetFromFS() завершается и продолжение метода GetBrowsers() снова постится в кастомный контекст. Это второй вызов Post(). Однако поскольку кастомный контекст просто запускает код в пуле потоков, эти продолжения работают уже в контексте пула потоков. Именно поэтому мы больше не видим вызовов кастомного контекста. Метод GetFromFS() начал исполняться в потоке с id=1. Однако само продолжение при этом выполнялось в потоке с id=3 по причине, описанной выше. Продолжение после вызова GetFromFS() было вызвано в этом же потоке (Post, thread id = 3). "Самый первый таск", как и любой другой, ждет специальный IO поток (т.н. IO completion port, IOCP). Но такие потоки ждут очень большое количество завершений, в т.ч. и тасков, поэтому говорить о "один таск -- один ждущий поток" не приходится. Нырнуть вглубь и почитать подробнее можно в статье на Хабре. Продолжение async метода выполняется в захваченном контексте. Если такого контекста нет (например, при вызове с ConfigureAwait(false)) -- продолжение выполняется в контексте пула потоков. Вызов продолжение в соответствующем контексте выполняется компилятором -- он генерирует соответствующий код с вызовом Post(). Компилятор разбирает async метод на составляющие (пролог+продолжения) и генерирует из них стейт-машину с переходами. Очень рекомендую посмотреть это выступление (или хотя бы слайды), а также ознакомиться с этим ответом. После этого фразы вроде "async/await занимается переключением контекста" должны пропасть из вашего обихода. Как резюме: вы получили смущающие результаты потому, что ваша реализация контекста, строго говоря, некорректна. По сути она аналогична контексту пула потоков, который используется для консольных приложений и в котором исполняются все продолжения, если не был обнаружен другой контекст.

Ответ 2



почему контекст привязывается не всегда? Проблема вашего контекста - в том, что он не умеет восстанавливать себя. Если вы пишите свой контекст - то вы сами должны позаботиться чтобы все продолжения запускались в нем же. кто ждет тот самый первый таск Если асинхронность правильная - то "самый первый" Task, как и все последующие, создается при помощи механизма TaskCompletionSource. Например, так (код привожу только для примера, в реальности надо еще исключения обрабатывать): Task VeryFirstTask() { var tcs = new TaskCompletionSource(); Action handler = null; handler = () => { tcs.SetResult(42); SomeEvent -= handler; }; SomeEvent += handler; return tcs.Task; } Не обязательно используются события - но идея одна и та же. Где-то сохраняется TaskCompletionSource, у которого в нужный момент вызывается SetResult/SetException/SetCanceled. что засовывает часть кода после await в пул потоков и когда (когда таск завершился или когда создался?)? Напрямую его туда засовывает контекст синхронизации. Опосредовано в этом участвует так же такая структура данных как TaskAwaiter (она хранит захваченный контекст синхронизации).

четверг, 9 января 2020 г.

Ограничить время жизни потока

#c_sharp #net #async_await


Есть список задач, каждую задачу нужно ограничить по времени. Если задача не успевает
выполниться, то пусть возвращается какое-то дефолтное значение.

class Program
{
    static void Main(string[] args)
    {
        var sw = new Stopwatch();
        sw.Start();

        foreach (var i in GetInts())
        {
            Console.WriteLine("result: " + i);
        }

        sw.Stop();
        Console.WriteLine("done " + sw.Elapsed.TotalMilliseconds.ToString("F2"));
        Console.ReadKey();
    }

    static List GetInts()
    {
        using (var cts = new CancellationTokenSource())
        {
            var tasks = new Task[3];

            tasks[1] = Task.Run(() =>
            {
                int i = 5000;
                Thread.Sleep(i);
                Console.WriteLine(i);
                return i;
            }, cts.Token);


            tasks[0] = Task.Run(() =>
            {
                int i = 3000;
                Thread.Sleep(i);
                Console.WriteLine(i);
                return i;
            }, cts.Token);

            tasks[2] = Task.Run(() =>
            {
                int i = 2000;
                Thread.Sleep(i);
                Console.WriteLine(i);
                return i;
            }, cts.Token);

            var delay = 1000;

            return tasks.Select(x => x.TimeoutAfter(delay, cts)).Select(x => x.Result).ToList();
        }
    }
}

public static class TaskExtensions
{
    public static async Task TimeoutAfter(this Task task,
int delay, CancellationTokenSource cts)
    {

        using (var timeoutCancellationTokenSource = new CancellationTokenSource())
        {

            var completedTask = await Task.WhenAny(task, Task.Delay(delay, timeoutCancellationTokenSource.Token));
            if (completedTask == task)
            {
                timeoutCancellationTokenSource.Cancel();
                return await task;
            }
            else
            {
                cts.Cancel();
                return default(TResult);
            }
        }
    }
}


Хотя в коде прописано ограничение в 1000мс, данный код выводит следующий результат:

2000
result: 0
result: 0
result: 2000
done 2010,83
3000
5000


Почему задача с 2с попадает в result? А если выставить delay = 3000, то выведутся
все задачи (хотя 5с не должна):

2000
3000
5000
result: 3000
result: 5000
result: 2000
done 5008,06

    


Ответы

Ответ 1



Смотрите. Вы не можете завершить бегущую задачу насильно, точно так же как вы не можете завершить насильно любой другой код. Для этого задача должна сотрудничать. Идея с использованием CancellationToken'а правильная, но не доведена до конца, отсюда и проблема. CancellationToken в Task.Run относится только к процессу запуска таска*. Когда таск запущен, он не сможет магическим образом узнать о том, что токен сработал, ему нужно самому проверять отмену токена. Например, так: var ct = cts.Token; tasks[0] = Task.Run(async () => // добавил async { int i = 3000; await Task.Delay(i, ct); // проверяем токен Console.WriteLine(i); return i; }, ct); Ещё по теме: Как работает CancellationToken в TaskFactory.StartNew Method (Action, CancellationToken)? *Почти. Ещё он ассоциируется с таском, и при выбросе исключения этим токеном таск переходит в состояние Cancelled, а не Faulted.

вторник, 7 января 2020 г.

Обновление свойства, привязанного к контролу, из другого потока

#c_sharp #wpf #mvvm #async_await


Всем привет. Есть пользовательский элемент управления. Упрощенная его разметка показана
ниже:

    
        

    


Codebehind данного элемента управления:
public partial class MyCanvas : UserControl
{
    public MapControl()
    {
        InitializeComponent();
    }

    static MapControl()
    {
        MyBackgroundProperty = DependencyProperty.
            Register("MyBackground", typeof(Brush), typeof(MapControl));
    }

    public static DependencyProperty MyBackgroundProperty;
    public Brush MyBackground
    {
        get { return (Brush)GetValue(MyBackgroundProperty);}

        set { SetValue(MyBackgroundProperty, value); }
    }
}

Далее данный пользовательский элемент управления используется в главном окне приложения
следующим образом:


   


Также есть следующая MainWindowViewModel, которая биндится к главному окну приложения:
public class MainViewModel : ViewModelBase
{
    private IMyService _service;

    private ImageBrush _image; 
    public ImageBrush Image 
    {
        get { return _image; }

        set 
        { 
           _image= value; 
           OnPropertyChanged("Image"); 
        }
    }

     private int _zoom; 
     public int Zoom 
     {
          get { return _zoom; }

          set 
          { 
               _zoom = value; 
               OnPropertyChanged("Zoom");

               // Обновляю картинку асинхронно.
               UpdateImage();                
          }
     }

    private async void UpdateImage()
    {
        // Данный метод возвращает новую картинку!
        // Картинка возвращается как Task
        Image = await _service.GetNewImageTaskAync();

        // Далее происходит исключение:
        // Необходимо создать DependencySource в 
        // том же потоке, в котором создан DependencyObject
    }
}

Проблема заключается в том, что если я обновляю свойство Image данными, полученными
из другого потока, то я получаю исключение "Необходимо создать DependencySource в том
же потоке, в котором создан DependencyObject".
Пробовал по-разному синхронизировать потоки, но ничего не получилось.
Подскажите, пожалуйста, как мне в свойство Image "положить" данные, созданные в другом
потоке.    


Ответы

Ответ 1



Кажется, понятно. Весь код, который вы привели, правильный. Ошибка должна быть в GetNewImageTaskAync. Либо нужно создавать image в UI-потоке, либо после создания вызвать на нём Freeze().

понедельник, 6 января 2020 г.

Разбор async await [дубликат]

#c_sharp #async_await


        
             
                
                    
                        
                            На этот вопрос уже даны ответы здесь:
                            
                        
                    
                
                        
                            Нужен async/await или не нужен?
                                
                                    (5 ответов)
                                
                        
                                Закрыт 1 год назад.
            
                    
Здравствуйте, извиняюсь за вопрос по этой теме т.к существует довольно огромное количество
статей об этих ключевых словах, но возможно такое количество статей и ввело меня в
некое не до понимание. 

Я просто хочу узнать правильно ли я все понял и задать походу пару вопросов. 


Начнем с async. Метод помеченный async разрешает нам внутри данного метода использовать
await и требует что бы возвращаемый тип имел метод GetAwaiter.
Ключевое слово await. Await ставится перед операцией которая выполняется во вторичном
потоке. Он позволяет не дожидаться пока вторичный поток (IO или обычный Thread) закончит
свое выполнение. Await просто оборачивает то, что идет после await в отдельный метод,
который по callback вызывается когда отработает вторичный поток. Тем самым первичный
поток продолжает выполняться дальше. А когда отработает вторичный поток из пула потоков
возьмется рандомный поток который довыполняет вторую часть метода.


В итого что это дает в приложениях


в приложениях с ui потоками(не до конца понятно что за ui поток, работаю исключительно
asp.net) наш поток продолжит выполнение и не заблочит действия пользователя;
в asp.net mvc у меня первичный поток освободится и будет работать только вторичный;
и последнее синтаксический сахар который скрывает всю логику которая это делает.


Вопросы


Правильно ли я все описал, действительно ли в этом смысл async await(разбить метод
на две части и до выполнять вторую часть как callback, тем самым не задерживая первичный
поток)?
Что за метод configureawait?
Почему многие говорят что эти ключевые слова не связаны с потоками. Ну как же await
то ставится перед операцией, которая будет выполняться во вторичном потоке, да и суть
то в чем, это конструкция была придумана что бы не держать первичный поток. Так что
связь тут самая прямая или я не прав?
Когда закончится выполняться вторичный поток, какой поток продолжит выполнять вторую
часть метода. Тот самый вторичный поток? Или будет взят любой поток из thread pool?

    


Ответы

Ответ 1



Для начала, ваши предположения. Ну как бы технически говоря да. Но есть ещё одна важная для понимания вещь: с ключевым словом async функция разбиваются на куски так, что между этими кусками функция вообще нигде не бежит. А каждый из кусков выполняется синхронно, как обыкновенная функция. Границы этих кусков идут по await. «Await ставится перед операцией которая выполняется во вторичном потоке.» — это неверно, await означает лишь, что в этой точке функция прекращает выполнение, и возобновляется тогда, когда Task, по отношению к которому производится await, завершается. Task, как мы уже видели, вовсе не означает «кусок кода, бегущий в каком-то потоке». Например, async-функция производит Task, который бежит в конкретном потоке лишь иногда, кусками. Теперь, ваши вопросы. В общем и целом правда, но не на одну часть, а на много. Представьте себе, например, такой код: async Task PrintSlowly(IEnumerable data) { Console.WriteLine("begin"); foreach (var n in data) { await Task.Delay(1000); Console.WriteLine(data); } Console.WriteLine("end"); } В этом методе у нас k + 1 кусок, где k — количество элементов, полученных из IEnumerable. Если await выполняется в UI-потоке (или в любом потоке, в котором есть свой TaskScheduler/SynchronizationContext), то следующий кусок функции перебрасывается для выполнения в тот же поток/SynchronizationContext. Если же вам это не нужно, вы можете сказать ConfigureAwait(false), что означает «мне безразлично, в каком контексте выполнятся остальные куски async-функции». При этом рантайм может заставить их выполняться где угодно, гарантий нет. Нет, операция, на которую делается await, не обязательно будет выполняться в другом потоке. Task не есть «кусок кода, который выполняется где-то ещё». Если эта операция реально не блокирует текущий поток, то да, текущий поток будет свободен. Но вы легко можете написать Task, который будет блокировать текущий поток: async Task Weird() { if (Math.Sin(0) == 42) await Task.Delay(1000); for (int x = 0; x < 1_000_000; x++) for (int y = 0; y < 1_000_000; y++) for (int z = 0; z < 1_000_000; z++) { if (x * x * x * x + y * y * y * y == z * z * z * z) throw new Exception("Ферма лох"); } } Где и как выполняется Task, определяется исключительно самим Task'ом. Единственный случай, когда Task целиком выполняется на побочном потоке, есть Task, запущенный через Task.Run() (в этом случае поток берётся из пула потоков). В остальных случаях забота о том, где именно выполняться, лежит на самом Task'e, async/await этим не занимается, и даже не требует, чтобы Task вообще где-то выполнялся. Task должен бы завершаться, это да (и то не обязан), а выполняться он может вообще нигде. Вот пример: static Task WaitTimer() { var tcs = new TaskCompletionSource(); new System.Threading.Timer(o => tcs.SetResult(0), null, 3000, System.Threading.Timeout.Infinite); return tcs.Task; } И да, обязательно почитайте ответы к этому вопросу.