Страницы

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

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

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

Создать свой Task.Run

#c_sharp #async #lambda #task


Каким образом в c# можно сделать async получив значение во внешний метод из лямбды?

Вот тестовый пример (он нерабочий):

    public async Task Test(MyClass data)
    {
        return Task.Run(() => {

            MyOtherClass result = null;
            bool endFlag = false;

            protocol.Invoke((MyOtherClass response) => // это async
            {
                endFlag = true;
                result = response;
            }, "uri/to/my/rpc", data);

            for (;;) // а это уже await
            {
                Thread.Sleep(200);
                if (endFlag)
                    break;
            }

            return result;

        });
    }


Задача функции Test - вытащить значение response из лямбды, и вернуть его, причём,
сделать это надо асинхронно, к циклу for(;;) хотелось бы перйти только когда потребуется
await. Подскажите, что нужно сделать, и почему этот код студия ругает?


    


Ответы

Ответ 1



Вероятно, вам надо что то вроде public Task Test(MyClass data) { return Task.Run(async () => { MyOtherClass result = null; bool endFlag = false; protocol.Invoke((MyOtherClass response) => // это async { endFlag = true; result = response; }, "uri/to/my/rpc", data); for (; ; ) // а это уже await { await Task.Delay(200); if (endFlag) break; } return result; }); } Но если вызов protocol.Invoke не блокирующий, то внутренний таск нам не нужен, у нас же уже асинхронный код. public async Task Test(MyClass data) { MyOtherClass result = null; bool endFlag = false; protocol.Invoke((MyOtherClass response) => // это async { result = response; endFlag = true; // И лучше бы флагать о конце операции в конце метода, чтобы не возникло проблем с race condition }, "uri/to/my/rpc", data); for (; ; ) // а это уже await { await Task.Delay(200); if (endFlag) break; } return result; } а вот уже совсем короткий вариант, что делает по сути то же самое public Task Test(MyClass data) { var tcs = new TaskCompletionSource(); protocol.Invoke((MyOtherClass response) => // это async { tcs.SetResult(response); }, "uri/to/my/rpc", data); return tcs.Task; }

Сортировка списка по частоте появления элементов

#python #сортировка #task #dict


Я пытаюсь решать задачи на checkio, и у меня появилась проблема.

Задача:

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

[4, 6, 2, 2, 6, 4, 4, 4] == [4, 4, 4, 4, 6, 6, 2, 2]
['bob', 'bob', 'carl', 'alex', 'bob'] == ['bob', 'bob', 'bob', 'carl', 'alex']
[17, 99, 42] == [17, 99, 42] 


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

Я использую код:

items1 = ["bob", "bob", "carl", "alex", "bob"]
items2 = [1, 2, 2, 1]
items3 = [4, 6, 2, 2, 6, 4, 4, 4]


def frequency_sort(items):
    result = []
    m = dict(pd.value_counts(items))
    print('Dictionary from list: ', m)
    for i in items:
        if items.count(i) == 1:
            return items
        elif items.count(i) == 0:
            return items
        else:
            for item in m:
                for i1 in range(0, m[item]):
                    result.append(item)
            return result
    return items


Результат:

Input data:  ['bob', 'bob', 'carl', 'alex', 'bob']
Dictionary from list:  {'bob': 3, 'alex': 1, 'carl': 1}
Result: ['bob', 'bob', 'bob', 'alex', 'carl']
**Must be: ['bob', 'bob', 'bob', 'carl', 'alex']**

Input data:  [1, 2, 2, 1]
Dictionary from list:  {2: 2, 1: 2}
Result: [2, 2, 1, 1]
**Must be: [1, 1, 2, 2]**

Input data:  [4, 6, 2, 2, 6, 4, 4, 4]
Dictionary from list:  {4: 4, 6: 2, 2: 2}
Result: [4, 4, 4, 4, 6, 6, 2, 2]


Код не работает. Есть 2 ошибки, они выделены.
Я не понимаю как словарь при преобразовании сортирует мой лист. Пытался отключить
сортировку: m = dict(pd.value_counts(items, sort=False)) Но получилось не очень :

Input data:  ['bob', 'bob', 'carl', 'alex', 'bob']
Dictionary from list:  {'carl': 1, 'bob': 3, 'alex': 1}
['carl', 'bob', 'bob', 'bob', 'alex']

Input data:  [1, 2, 2, 1]
Dictionary from list:  {1: 2, 2: 2}
[1, 1, 2, 2]

Input data:  [4, 6, 2, 2, 6, 4, 4, 4]
Dictionary from list:  {2: 2, 4: 4, 6: 2}
[2, 2, 4, 4, 4, 4, 6, 6]


Как решить задачу?
Пробовал сделать через листы, но запнулся.
    


Ответы

Ответ 1



Почему бы не так. Пробегаемся по всем уникальным значениям в порядке появления и заполняем в результат столько, сколько их в исходном листе result = [] for x in [x for i, x in enumerate(items) if i == items.index(x)]: result.extend([x]*items.count(x)) или даже вот так, что по сути тоже самое def frequency_sort(items): result = [] for i, x in enumerate(items): if i == items.index(x): result.extend([x]*items.count(x)) return result Update def frequency_sort(items): temp = [] for i, x in enumerate(items): if i == items.index(x): temp.append([x, [items.count(x), len(items)-i]]) temp = sorted(temp, key = lambda x: x[1], reverse=True) result = [] for x in temp: result.extend([x[0]]*x[1][0]) return result

Ответ 2



Возможно, такой вариант вас устроит: from collections import Counter from itertools import chain def freqlst(lst: list)-> list: return list(chain(*[ [k,]*v for k,v in Counter(lst).items()])) Пррверяем: print(freqlst([4, 6, 2, 2, 6, 4, 4, 4])) # [4, 4, 4, 4, 6, 6, 2, 2] print(freqlst(['bob', 'bob', 'carl', 'alex', 'bob'])) # ['bob', 'bob', 'bob', 'carl', 'alex'] print(freqlst([17, 99, 42])) # [17, 99, 42]

Ответ 3



Я решил задачу следующим образом (UPD): def frequency_sort(items: list) -> list: counts = {x:items.count(x) for x in items} sorted_counts = {k: counts[k] for k in sorted(counts.keys(), key=counts.get, reverse=True)} result = [x for x in sorted_counts for _ in range(sorted_counts[x])] return result Примечание: данный код работает, начиная с версии 3.7. counts — это словарь, в котором ключами являются элементы списка items, а значениями — количество вхождений соответствующего ключа в список items. sorted_counts — это словарь counts, отсортированный по значениям ключей в обратном порядке. Словари, начиная с Python 3.7 сохраняют порядок, поэтому отлично подходят под наши цели и удовлетворяют одному из условий задачи. Всё что остаётся — это преобразовать полученный словарь в новый список (result). Этим занимается списковое включение, которое можно переписать следующим образом: result = [] for x in sorted_counts: for _ in range(sorted_counts[x]): result.append(x) Тесты: print(frequency_sort(items1)) # ['bob', 'bob', 'bob', 'carl', 'alex'] print(frequency_sort(items2)) # [1, 1, 2, 2] print(frequency_sort(items3)) # [4, 4, 4, 4, 6, 6, 2, 2] # UPD: Тест из комментария print(frequency_sort(items4)) # [4, 4, 4, 4, 2, 2, 2, 6, 6]

Ответ 4



lst = [4, 6, 2, 2, 6, 4, 4, 4] result = [r for s in sorted(set(lst), key=lst.index) for r in (a for a in lst if a == s)]

Ответ 5



def frequency_sort(items): temp = [] sort_dict = {x: items.count(x) for x in items} for k, v in sorted(sort_dict.items(), key=lambda x: x[1], reverse=True): temp.extend([k] * v) return temp

суббота, 11 января 2020 г.

Есть ли возможность при запуске .bat файла установить факт был ли он запущен вручную или автоматически(Task sheduler)?

#windows #bat #автоматизация #task #users


OC: Windows 2008 R2 Enterprise

Моя ситация: С помощью Task Sheduler ежедневно запускается определенный .bat файл.
Иногда есть потребность запускать его вручную.

Моя задача: При ручном запуске от Юзера, после прохождения команд оставить консоль
открытую (Pause) чтобы прочитать логи. При этом после запуска с помощью Task Sheduler
процесс должен терминироваться без Паузы. %USERNAME% не подходит, так как TS запускает
бптник от того же пользователя, что и запускает его вручную.

Есть ли возможность определить запускает батник Task Sheduler?
Может можно терминировать программу другим способом? 
Например через минуту после того как она дойдет до конца?
    


Ответы

Ответ 1



Вариант 1. Настройте запуск этого самого батника с параметром. В самом батнике проверяйте параметр: if "%1"=="" pause или вот так: if not "%1"=="nopause" pause Вариант 2. Научите пользователя запускать батник не двойным кликом - а как полагается, из командной строки. Командная строка не закрывается просто так. Вариант 3. Научите пользователя запускать батник используя FAR Manager. Там можно посмотреть консольный вывод скрыв панели (Ctrl+O) Но самым "красивым" способом я считаю создание отдельного ярлыка для батника. В этом ярлыке настраивается запуск файла через cmd с ключом /k. Ключ /k означает "выполнить команду и не закрывать консоль":

Ответ 2



Есть ли возможность определить запускает батник Task Sheduler? В принципе, эту информацию можно вытащить (см. комментарий @avp к посту) Видно, что можно поискать родителя и сравнить с svchost.exe. Однако, мне кажется, что лучше решить вашу задачу другим способом. Можно передавать при запуске из таск шедулера дополнительный параметр, который опускать при ручном запуске. Когда батник получит управление -- проверить, с каким параметром был вызван. Если вручную -- запустить дополнительную команду (паузу). По-моему весьма тривиальный и логичный воркэраунд.

Ответ 3



В Task Scheduler запускать VbScript, который будет запускать батник и после отработки процесса завершать его. Терминировать процесс можно так. var WshShell = WScript.CreateObject("WScript.Shell"); WshShell.Run("taskkill /IM iexplore.exe", 0);

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

Туманности и мои пробелы в знаниях с 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 г.

Task vs Thread: на каком ядре

#c_sharp #многопоточность #tpl #task


Про Task:


  Данная библиотека позволяет распараллелить задачи и выполнять их сразу
  на нескольких процессорах, если на целевом компьютере имеется
  несколько ядер. Кроме того, упрощается сама работа по созданию новых
  потоков. Поэтому начиная с .NET 4.0. рекомендуется использовать именно
  TPL и ее классы для создания многопоточных приложений, хотя
  стандартные средства и класс Thread по-прежнему находят широкое
  применение.


Источник: ссылка



Изначально кол-во потоков в пуле потоков равно числу ядер процессора. Вопрос: каждый
поток из потока пулов будет выполняться на "своем" ядре?


А как дела обстоят с обычными потоками - Thread (понимаю, что Task - более высокая
абстракция Thread)? Новый Thread может выполняться как на ядре, где выполняется главный
поток, так и на другом ядре, т.е. не гарантирует выполнение на новом ядре?
    


Ответы

Ответ 1



В цитате явно смесь процессоров и ядер. Но распараллеливаются обычно по ядрам, а эти ядра могут принадлежать разным процессорам. Изначально кол-во потоков в пуле потоков равно числу ядер процессора. на усмотрение библиотеки. Вопрос: каждый поток из потока пулов будет выполняться на "своем" ядре? можно сделать так, что бы каждый поток исполнялся только на своем ядре (осознанно привязав их), но так обычно не делают - планировщик ОС обычно несколько умнее и будет их разбрасывать по ядрам по своему разумению. А как дела обстоят с обычными потоками - Thread (понимаю, что Task - более высокая абстракция Thread)? Да, Task - абстракция, которая прячет от программиста детали. Просто таска, это функция, которая выполняется внутри потока. Синхронизация, очереди и получения результата прячутся библиотекой. Новый Thread может выполняться как на ядре, где выполняется главный поток, так и на новом? Там, где будет удобнее ОС разместить его. Более того, поток может "гулять" по ядрам.

суббота, 14 декабря 2019 г.

Передача сообщений между потоками - C#

#c_sharp #wpf #многопоточность #task


Есть у меня простой класс-логгер:

static class Logger
{
    public delegate void Message(string msg);

    static public event Message OnMessage;

    static public void SendMessage(string msg)
    {
        OnMessage?.Invoke(msg);
    }
}


Я из любых мест приложения отправляю ему сообщения по типу: Logger.SendMessage("Получена
команда на запуск");

При загрузке window (WPF) я подписываюсь на события логгера и вывожу лог в textbox

Пока приложение было однопоточным всё отлично работало, но теперь методы отлажены
и надо всё распараллелить (идет обращение к 40 БД на разных хостах поэтому всё хорошо
параллелится)

Но теперь возникла проблема - при попытке прочитать отправленное сообщение из другого
потока возникает Exception 


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


Как это грамотно и с малой кровью исправить?
    


Ответы

Ответ 1



Смотрите. Проблема в том, что события доставляются в том потоке, который отправляет события. Поэтому у вас подписчики событий получают событие каждый раз в разных потоках. Если подписчик — UI-код, который просто выводит текст в UI, то при приходе сообщения из неглавного потока происходит проблема. Есть несколько путей починки вашего кода. Можно привязать логгер к главному потоку. При этом сообщения будут доставляться только в главном потоке, и соответственно UI-код будет всегда работать «как надо». static class Logger { static Lazy dispatcher = new Lazy(() => Application.Current.Dispatcher); public delegate void Message(string msg); static public event Message OnMessage; static public void SendMessage(string msg) { if (dispatcher.Value.CheckAccess()) OnMessage?.Invoke(msg); else dispatcher.Value.InvokeAsync(() => OnMessage?.Invoke(msg)); } } Это, наверное, не самое лучшее архитектурное решение, т. к. при этом логгер получается зависимым от WPF, то есть модель получает зависимость от VM (что не позволит использовать её повторно в других программах). Зато этот метод решает задачу наиболее просто: другие переделки при этом не нужны. Можно считать логгер не привязанным ни к какому потоку, тогда UI-код должен проверять, в каком потоке он запущен, и при необходимости пользоваться Dispatcher.InvokeAsync. Logger.OnMessage += s => { if (Dispatcher.CheckAccess()) LogContainer.Text += (s + "\n"); else Dispatcher.InvokeAsync(() => LogContainer.Text += (s + "\n")); }; Это более правильный подход, но здесь придётся потенциально править все места, где происходит подписка на сообщения от логгера. Впрочем, такое место в программе, судя по всему, одно. Вы можете использовать модные в этом сезоне Reactive Extensions, и переписать ваш класс на них: using System.Reactive.Linq; using System.Reactive.Subjects; static class Logger { static ISubject subject = Subject.Synchronize(new Subject()); public static IObservable Messages => subject; static public void SendMessage(string msg) => subject.OnNext(msg); } Подписка при этом выглядит так: Logger.Messages.ObserveOnDispatcher().Subscribe(s => LogContainer.Text += (s + "\n")); Максимальная гибкость, LINQ на сообщениях, доставка в произвольный поток, навесные плюшки наподобие подавления слишком частых или повторяющихся сообщений поставляется в комплекте, бонусом ощущение собственной крутости, функциональности и трендовости. Минус — вам придётся-таки разобраться с этим самым Rx (муа-ха-ха!). Или это можно считать плюсом, да. (Think positive.) Не забудьте подключить из nuget System.Reactive.Core, System.Reactive.Interfaces, System.Reactive.Linq и System.Reactive.Windows.Threading.

C#. Thread.CurrentThread.IsBackground

#c_sharp #многопоточность #task


Вопрос, наверняка, окажется глупым и не носящем практической ценности, но тем не
менее. Сейчас вплотную разбираюсь с многопоточностью и параллельным программированием.
На всех ресурсах пишут приблизительно следующее.
"Приложение запускается всегда в приоритетном потоке...." и 
"При завершении всех приоритетных потоков, все фоновые также завершаются, даже, если
не выполнили свои задачи". 
Отсюда сабж, почему приложение не завершается, когда явно указывается

Thread.CurrentThread.IsBackground = true;


?

Просьба помочь разобраться. 
И прицепом еще один. В чем практическое отличие 

Task task = new Task(MyTask);


от

Thread tr = new Thread(new ThreadStart(MyTask));


?

я так понял, Задачи в целом эффективнее распределяются за счет планировщика задач
    


Ответы

Ответ 1



По первой части вопроса, думаю, это пробел в документации. В документации говорится, что происходит при завершении основного, нефонового потока, но ничего не говорится о случае, когда поток просто прекращает быть основным, не завершаясь при этом. Я не нашёл ссылок на это ни в документации, ни в спецификации языка. По поводу второй части вопроса: Thread — это физический (кроме очень крайних частных случаев) поток выполнения, имеющий прямое соответствие с потоками операционной системы. Task — абстракция задания, которое будет когда-либо выполнено, и соответствующий заданию код может бежать в одном или нескольких, иногда даже в нулевом количестве потоков. Для случая использования, который вы привели, разница в эффекте незначительна: Task в том виде, в котором он вызывается в вашем с коде из вопроса, выполняется на потоке из пула потоков, а Thread создаётся вне пула. Но есть разница в использовании. Для Thread'а вы можете лишь вызвать Join, и синхронно дождаться его завершения. Для Task вы можете, кроме того, установить продолжение посредством ContinueWith, которое будет выполнено в будущем, по завершению этого Task'а. Кроме того, вы можете дождаться о окончания Task'а асинхронно, используя await. Вы можете снаружи поймать и обработать исключение, произошедшее при выполнении Task'а. Всё это с Thread'ом недоступно.

четверг, 12 декабря 2019 г.

Как работает CancellationToken в TaskFactory.StartNew Method (Action, CancellationToken)?

#c_sharp #net #task


Делаю:

Task.Factory.StartNew(() =>Method(),ct)


В другом месте вызываю tokenSource.Cancel();, но ничего не происходит.

Мне казалось, что такая перегрузка должна полностью задачу снимать.

Получается, что нужно токен передавать непосредственно в сам метод и там производить
анализ?

Можно ли как-нибудь отменить задачу, непередавая во внутрь токен?
    


Ответы

Ответ 1



Смотрите. Вы не можете «отменить» уже бегущий код — по тем же причинам, по которым вы не можете «убить» бегущий thread. Поэтому код, запускаемый через Task.Run, добежит до конца, если только он сам не будет анализировать токен и не отменит себя сам. Однако, таск не будет запущен, если токен находится уже в отменённом состоянии. Поскольку запуск Task'а — расходная штука, это неплохая оптимизация. Кроме того, таск при этом будет ассоциирован с этим токеном, и если код в Method бросит исключение через token.ThrowIfCancellationRequested, это исключение будет считаться относящимся к таску. Тем самым таск будет завершён в состоянии Cancelled, а не Faulted. Источник информации: https://social.msdn.microsoft.com/Forums/en-US/c2f614f6-c96c-4821-84cc-050b21aaee45/taskfactorystartnew-cancellation-token-parameter?forum=parallelextensions Воспроизводящий пример: async Task Run() { var cts = new CancellationTokenSource(); var ct = cts.Token; var t = Task.Run((Action)(() => // каст нужен! пояснение в конце ответа { while (true) { ct.ThrowIfCancellationRequested(); Thread.Sleep(200); } }), cts.Token); await Task.Delay(400); cts.Cancel(); try { await t; } catch (TaskCanceledException ex) { Console.WriteLine($"caught TaskCanceledException, task status = {t.Status}"); } catch (OperationCanceledException ex) { Console.WriteLine($"caught OperationCanceledException, task status = {t.Status}"); } } Этот код попадает в catch (OperationCanceledException ex), и состояние таска — Canceled. А если убрать токен, то мы попадаем также в catch (OperationCanceledException ex), но состояние — Faulted. В этом случае рантайм считает, что произошла не отмена таска, а просто какое-то постороннее исключение. (В TaskCanceledException мы попадаем, если таск был отменён до запуска.) (Ошибка в предыдущей версии кода была в том, что не было явного приведения к Action [которое практически никогда не нужно!], и ввиду бесконечного цикла внутри типовыводитель не мог решить, это перегрузка с Action или перегрузка с Func! В результате он выбирал не то, что нужно, и рантайм считал, что отменён не таск, а процесс его создания.)

Ответ 2



Да, токен необходимо передавать в метод, и там периодически вызывать ThrowIfCancellationRequested(). Однако, это не отменяет необходимости передавать его вторым параметром StartNew(). Это делается на тот случай, если отмена операции запрошена раньше чем начал выполняться ваш метод (а так же для того, чтобы связать таск с токеном). Примерно так: var cts = new CancellationTokenSource(); Task.Factory.StartNew(() => { while (true) { cts.Token.ThrowIfCancellationRequested(); Console.WriteLine("I'm working..."); } }, cts.Token); cts.CancelAfter(10); Если была запрошена отмена, то метод ThrowIfCancellationRequested() кинет OperationCanceledException. При желании, его можно поймать и сообщить юзеру, что операция успешно отменена, например. Если хочется, то можно обходиться без эксепшена, а просто в методе проверять: if (token.IsCancellationRequested) { /*выходим из метода*/ } Но тогда сама задача не будет знать о том, что её отменили. В свойстве Status у неё будет TaskStatus.RanToCompletion вместо TaskStatus.Canceled. UPD: Дополнительный материал по теме: https://msdn.microsoft.com/en-us/library/dd997396(v=vs.110).aspx

четверг, 5 декабря 2019 г.

CancellationToken: почему структура?

#c_sharp #tpl #task


Почему CancellationToken реализован как структура?
Ведь структура является типом значения, как тогда реализован данный механизм?


static void Main(string[] args)
{
    CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
    CancellationToken token = cancelTokenSource.Token;

    Task task1 = new Task(() => Factorial(5, token));
    task1.Start();

    cancelTokenSource.Cancel();
}

static void Factorial(int x, CancellationToken token)
{
    int result = 1;
    for (int i = 1; i <= x; i++)
    {
        if (token.IsCancellationRequested)
        {
            Console.WriteLine("Операция прервана токеном");
            return;
        }

        result *= i;
        Console.WriteLine("Факториал числа {0} равен {1}", i, result);
        Thread.Sleep(5000);
    }
}


Ведь структура является типом значения и создается копия объекта при передачи параметра
CancellationToken token
    


Ответы

Ответ 1



Почему CancellationToken реализован как структура? Для борьбы за эффективность. В большинстве случаев, да, CancellationToken вполне мог бы быть и классом, одна аллокация ничего не меняет, так как многопоточный код обычно некритичен к паре лишних мелких аллокаций. Но ведь CancellationToken задумывался как общий механизм отмены. И существуют случаи, в которых аллокации критичны для пользователей. Если бы CancellationToken был классом, то в этих случаях пользователям фреймворка проходилось бы пользоваться самописной структурой, и, хуже того, они не смогли бы пользоваться библиотечными методами. (Например, если пользователь хочет в метод, который ожидает CancellationToken, передать CancellationToken.None, потому что ему нужна скорость и не нужна отмена.) Так что разработчики решили облегчить нам, пользователям, жизнь, и сделали CancellationToken таки структурой. По поводу реализации механизма: да, разработчики нарушили семантику, и CancellationToken ведёт себя как типичный класс, а не как типичная структура. Они реализовали это таким образом. В CancellationToken есть единственное поле private CancellationTokenSource m_source; (ссылка на исходники). В проверке на равенство сравнивается m_source, и при копировании m_source копируется тоже, так что копия токена ведёт себя как оригинал, и тем самым неотличима от него. В частности, если оригинал токена отменён, то его копия — тоже.

Настройка планировщика на подключение флешки

#windows #powershell #task #wmi #background_task


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


Ответы

Ответ 1



Я слегка слукавил, без планировщика не обойтись, хотя, наверное, можно и через автозагрузку решить всё это. Я для одного пожилого сотрудника когда-то использовал вот такой скрипт, который срабатывал, если вставлялась флэшка, автоопределяемая с буквой T и меткой "BP_flahka". Если буква диска не важна, то вместо if ($driveLetter -eq 'T:' -and $driveLabel -eq 'BP_flashka') можно использовать просто if ($driveLabel -eq 'ТутМеткаВашейФлэшки'). Код скрипта: #Requires -version 2.0 Register-WmiEvent -Class win32_VolumeChangeEvent -SourceIdentifier volumeChange write-host (get-date -format s) " Beginning script..." do{ $newEvent = Wait-Event -SourceIdentifier volumeChange $eventType = $newEvent.SourceEventArgs.NewEvent.EventType $eventTypeName = switch($eventType) { 1 {"Configuration changed"} 2 {"Device arrival"} 3 {"Device removal"} 4 {"docking"} } write-host (get-date -format s) " Event detected = " $eventTypeName if ($eventType -eq 2) { $driveLetter = $newEvent.SourceEventArgs.NewEvent.DriveName $driveLabel = ([wmi]"Win32_LogicalDisk='$driveLetter'").VolumeName write-host (get-date -format s) " Drive name = " $driveLetter write-host (get-date -format s) " Drive label = " $driveLabel # Запустить, если буква диска и метка совпали с нужными значениями if ($driveLetter -eq 'T:' -and $driveLabel -eq 'BP_flashka') { write-host (get-date -format s) " Starting task in 3 seconds..." start-sleep -seconds 3 start-process "E:\sync.bat" } } Remove-Event -SourceIdentifier volumeChange } while (1-eq1) #Идём в цикле дальше Unregister-Event -SourceIdentifier volumeChange Дальше всё просто. В планировщике создаём новое задание, с такими параметрами: Триггер: At log on Действие: Start a program Program/script: powershell Аргументы: -WindowStyle Hidden -ExecutionPolicy Unrestricted -File "Диск:\Путь\имя файла со скриптом.ps1" Update 1. Проверок для вставляемого хранилища можно устроить массу. Например: # узнаем тип файловой системы флэшки $fileSystem = ([wmi]"Win32_LogicalDisk='$driveLetter'").FileSystem # проверяем, сколько свободного места осталось (в байтах) на флэшке $freeSpace = ([wmi]"Win32_LogicalDisk='$driveLetter'").FreeSpace # или общий размер всей флэшки: $size = ([wmi]"Win32_LogicalDisk='$driveLetter'").Size # и так далее, а затем просто проверяем нужный нам параметр, например: if ($fileSystem -eq 'NTFS') { # в этом случае бэкап можно делать одним большим файлом } # и так далее Update 2. Работать будет, начиная с Windows Vista/Server 2008

понедельник, 17 июня 2019 г.

Как убить Task который долго выполняется?

Есть запрос, который может выполняться до часа. Что бы не блокировать интерфейс пользователя, он проходит через Task. Если пользователю надоело ждать, надо этот запрос отменить. Как правильно это сделать, что бы соединение не висело целый час нагружая БД и пользователь их не создавал кучу? Может есть какие-то альтернативные варианты, кроме Task?
private void UpdateGrid(){ SomeRows rows; Task task = new Task(() => { rows = IDBConnectInstance.SelectRows(); }); task.ContinueWith(() => { dataGridView.DataSource = rows; }, TaskScheduler.FromCurrentSynchronizationContext()); }
UPD:
Каких либо дополнительных точек нету, сам IDBConnectInstance представляет собой интерфейс из сторонней библиотеки. То есть он входит в функцию библиотеки IDBConnectInstance.SelectRows и останавливается в ожидании завершения. Если бы была возможность обработать CancellationToken, вопрос бы не возник.
IDBConnect { SelectRows(); }
public class DBConnect: IDBConnect { public List SelectRows() { String query = "Очень долгий SELECT из БД"; List result = new List(); using(OracleConnection dbconn = new OracleConnection(ConnectionString)) { dbconn.Open();
OracleCommand command = new OracleCommand(query, dbconn); OracleDataReader reader = command.ExecuteReader(); while (reader.Read()) { SomeClass someClass = new someClass() { SomeField = reader[0], SomeField = reader[1], ... } result.Add(someClass); } reader.Close(); dbconn.Close(); } return result; } }
Пока есть только мысль вернуться на Thread и использовать устаревший Thread.Abort()


Ответ

Желательно дать пользователю возможность отмены, по команде.
private CancellationTokenSource _tokenSource; private bool _isWorked = false;
private void CancelUpdateGrid() { _tokenSource.Cancel(); }
private void UpdateGrid() { if (_isWorked) { return; }
_isWorked = true;
SomeRows rows;
// создаём CancellationTokenSource который отменит операцию _tokenSource = new CancellationTokenSource();
// В задачу передадим нужный нам токен. Task task = Task.Run(() => { var t = Thread.CurrentThread; using (cancellationSource.Token.Register(t.Abort)) { rows = IDBConnectInstance.SelectRows(); } }, _tokenSource.Token).ContinueWith(t => { dataGridView.DataSource = rows; _isWorked = false; }); }
Приведённый мною код упрощённый для понятия концепции с отменой.

среда, 12 июня 2019 г.

Task.IsComplited до реального завершения задачи

Создаю кучу Task - в каждом игровой цикл, помещаю их в List
GamesList.Add(gp.ContinueWith(t=>GamesList.Remove(t)));
Но они почему-то удаляются из списка до того как игровой цикл завершится - игра при этом идет спокойненько. Wtf?


Ответ

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

Попробуйте заменить GamesList на потокобезопасную коллекцию, и используйте
GamesList.Add(gp); gp.ContinueWith(t=>GamesList.Remove(t));

воскресенье, 14 апреля 2019 г.

Создать свой Task.Run

Каким образом в c# можно сделать async получив значение во внешний метод из лямбды?
Вот тестовый пример (он нерабочий):
public async Task Test(MyClass data) { return Task.Run(() => {
MyOtherClass result = null; bool endFlag = false;
protocol.Invoke((MyOtherClass response) => // это async { endFlag = true; result = response; }, "uri/to/my/rpc", data);
for (;;) // а это уже await { Thread.Sleep(200); if (endFlag) break; }
return result;
}); }
Задача функции Test - вытащить значение response из лямбды, и вернуть его, причём, сделать это надо асинхронно, к циклу for(;;) хотелось бы перйти только когда потребуется await. Подскажите, что нужно сделать, и почему этот код студия ругает?


Ответ

Вероятно, вам надо что то вроде
public Task Test(MyClass data) { return Task.Run(async () => {
MyOtherClass result = null; bool endFlag = false;
protocol.Invoke((MyOtherClass response) => // это async { endFlag = true; result = response; }, "uri/to/my/rpc", data);
for (; ; ) // а это уже await { await Task.Delay(200); if (endFlag) break; }
return result;
}); }
Но если вызов protocol.Invoke не блокирующий, то внутренний таск нам не нужен, у нас же уже асинхронный код.
public async Task Test(MyClass data) { MyOtherClass result = null; bool endFlag = false;
protocol.Invoke((MyOtherClass response) => // это async { result = response; endFlag = true; // И лучше бы флагать о конце операции в конце метода, чтобы не возникло проблем с race condition }, "uri/to/my/rpc", data);
for (; ; ) // а это уже await { await Task.Delay(200); if (endFlag) break; }
return result; }
а вот уже совсем короткий вариант, что делает по сути то же самое
public Task Test(MyClass data) { var tcs = new TaskCompletionSource(); protocol.Invoke((MyOtherClass response) => // это async { tcs.SetResult(response); }, "uri/to/my/rpc", data); return tcs.Task; }

вторник, 5 марта 2019 г.

Есть ли возможность при запуске .bat файла установить факт был ли он запущен вручную или автоматически(Task sheduler)?

OC: Windows 2008 R2 Enterprise
Моя ситация: С помощью Task Sheduler ежедневно запускается определенный .bat файл. Иногда есть потребность запускать его вручную.
Моя задача: При ручном запуске от Юзера, после прохождения команд оставить консоль открытую (Pause) чтобы прочитать логи. При этом после запуска с помощью Task Sheduler процесс должен терминироваться без Паузы. %USERNAME% не подходит, так как TS запускает бптник от того же пользователя, что и запускает его вручную.
Есть ли возможность определить запускает батник Task Sheduler? Может можно терминировать программу другим способом? Например через минуту после того как она дойдет до конца?


Ответ

Вариант 1. Настройте запуск этого самого батника с параметром. В самом батнике проверяйте параметр:
if "%1"=="" pause
или вот так:
if not "%1"=="nopause" pause
Вариант 2. Научите пользователя запускать батник не двойным кликом - а как полагается, из командной строки. Командная строка не закрывается просто так.
Вариант 3. Научите пользователя запускать батник используя FAR Manager. Там можно посмотреть консольный вывод скрыв панели (Ctrl+O)

Но самым "красивым" способом я считаю создание отдельного ярлыка для батника. В этом ярлыке настраивается запуск файла через cmd с ключом /k. Ключ /k означает "выполнить команду и не закрывать консоль":

понедельник, 18 февраля 2019 г.

Task vs Thread: на каком ядре

Про Task:
Данная библиотека позволяет распараллелить задачи и выполнять их сразу на нескольких процессорах, если на целевом компьютере имеется несколько ядер. Кроме того, упрощается сама работа по созданию новых потоков. Поэтому начиная с .NET 4.0. рекомендуется использовать именно TPL и ее классы для создания многопоточных приложений, хотя стандартные средства и класс Thread по-прежнему находят широкое применение.
Источник: ссылка

Изначально кол-во потоков в пуле потоков равно числу ядер процессора. Вопрос: каждый поток из потока пулов будет выполняться на "своем" ядре? А как дела обстоят с обычными потоками - Thread (понимаю, что Task - более высокая абстракция Thread)? Новый Thread может выполняться как на ядре, где выполняется главный поток, так и на другом ядре, т.е. не гарантирует выполнение на новом ядре?


Ответ

В цитате явно смесь процессоров и ядер. Но распараллеливаются обычно по ядрам, а эти ядра могут принадлежать разным процессорам.
Изначально кол-во потоков в пуле потоков равно числу ядер процессора.
на усмотрение библиотеки.
Вопрос: каждый поток из потока пулов будет выполняться на "своем" ядре?
можно сделать так, что бы каждый поток исполнялся только на своем ядре (осознанно привязав их), но так обычно не делают - планировщик ОС обычно несколько умнее и будет их разбрасывать по ядрам по своему разумению.
А как дела обстоят с обычными потоками - Thread (понимаю, что Task - более высокая абстракция Thread)?
Да, Task - абстракция, которая прячет от программиста детали. Просто таска, это функция, которая выполняется внутри потока. Синхронизация, очереди и получения результата прячутся библиотекой.
Новый Thread может выполняться как на ядре, где выполняется главный поток, так и на новом?
Там, где будет удобнее ОС разместить его. Более того, поток может "гулять" по ядрам.

понедельник, 12 ноября 2018 г.

Передача сообщений между потоками - C#

Есть у меня простой класс-логгер:
static class Logger { public delegate void Message(string msg);
static public event Message OnMessage;
static public void SendMessage(string msg) { OnMessage?.Invoke(msg); } }
Я из любых мест приложения отправляю ему сообщения по типу: Logger.SendMessage("Получена команда на запуск");
При загрузке window (WPF) я подписываюсь на события логгера и вывожу лог в textbox
Пока приложение было однопоточным всё отлично работало, но теперь методы отлажены и надо всё распараллелить (идет обращение к 40 БД на разных хостах поэтому всё хорошо параллелится)
Но теперь возникла проблема - при попытке прочитать отправленное сообщение из другого потока возникает Exception
"Вызывающий поток не может получить доступ к данному объекту, так как владельцем этого объекта является другой поток."
Как это грамотно и с малой кровью исправить?


Ответ

Смотрите.
Проблема в том, что события доставляются в том потоке, который отправляет события. Поэтому у вас подписчики событий получают событие каждый раз в разных потоках. Если подписчик — UI-код, который просто выводит текст в UI, то при приходе сообщения из неглавного потока происходит проблема.
Есть несколько путей починки вашего кода.
Можно привязать логгер к главному потоку. При этом сообщения будут доставляться только в главном потоке, и соответственно UI-код будет всегда работать «как надо».
static class Logger { static Lazy dispatcher = new Lazy(() => Application.Current.Dispatcher);
public delegate void Message(string msg);
static public event Message OnMessage;
static public void SendMessage(string msg) { if (dispatcher.Value.CheckAccess()) OnMessage?.Invoke(msg); else dispatcher.Value.InvokeAsync(() => OnMessage?.Invoke(msg)); } }
Это, наверное, не самое лучшее архитектурное решение, т. к. при этом логгер получается зависимым от WPF, то есть модель получает зависимость от VM (что не позволит использовать её повторно в других программах). Зато этот метод решает задачу наиболее просто: другие переделки при этом не нужны. Можно считать логгер не привязанным ни к какому потоку, тогда UI-код должен проверять, в каком потоке он запущен, и при необходимости пользоваться Dispatcher.InvokeAsync
Logger.OnMessage += s => { if (Dispatcher.CheckAccess()) LogContainer.Text += (s + "
"); else Dispatcher.InvokeAsync(() => LogContainer.Text += (s + "
")); };
Это более правильный подход, но здесь придётся потенциально править все места, где происходит подписка на сообщения от логгера. Впрочем, такое место в программе, судя по всему, одно. Вы можете использовать модные в этом сезоне Reactive Extensions, и переписать ваш класс на них:
using System.Reactive.Linq; using System.Reactive.Subjects;
static class Logger { static ISubject subject = Subject.Synchronize(new Subject()); public static IObservable Messages => subject; static public void SendMessage(string msg) => subject.OnNext(msg); }
Подписка при этом выглядит так:
Logger.Messages.ObserveOnDispatcher().Subscribe(s => LogContainer.Text += (s + "
"));
Максимальная гибкость, LINQ на сообщениях, доставка в произвольный поток, навесные плюшки наподобие подавления слишком частых или повторяющихся сообщений поставляется в комплекте, бонусом ощущение собственной крутости, функциональности и трендовости. Минус — вам придётся-таки разобраться с этим самым Rx (муа-ха-ха!). Или это можно считать плюсом, да. (Think positive.)
Не забудьте подключить из nuget System.Reactive.Core, System.Reactive.Interfaces, System.Reactive.Linq и System.Reactive.Windows.Threading

вторник, 23 октября 2018 г.

C#. Thread.CurrentThread.IsBackground

Вопрос, наверняка, окажется глупым и не носящем практической ценности, но тем не менее. Сейчас вплотную разбираюсь с многопоточностью и параллельным программированием. На всех ресурсах пишут приблизительно следующее. "Приложение запускается всегда в приоритетном потоке...." и "При завершении всех приоритетных потоков, все фоновые также завершаются, даже, если не выполнили свои задачи". Отсюда сабж, почему приложение не завершается, когда явно указывается
Thread.CurrentThread.IsBackground = true;
?
Просьба помочь разобраться. И прицепом еще один. В чем практическое отличие
Task task = new Task(MyTask);
от
Thread tr = new Thread(new ThreadStart(MyTask));
?
я так понял, Задачи в целом эффективнее распределяются за счет планировщика задач


Ответ

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

По поводу второй части вопроса:
Thread — это физический (кроме очень крайних частных случаев) поток выполнения, имеющий прямое соответствие с потоками операционной системы. Task — абстракция задания, которое будет когда-либо выполнено, и соответствующий заданию код может бежать в одном или нескольких, иногда даже в нулевом количестве потоков.
Для случая использования, который вы привели, разница в эффекте незначительна: Task в том виде, в котором он вызывается в вашем с коде из вопроса, выполняется на потоке из пула потоков, а Thread создаётся вне пула. Но есть разница в использовании
Для Thread'а вы можете лишь вызвать Join, и синхронно дождаться его завершения.
Для Task вы можете, кроме того, установить продолжение посредством ContinueWith, которое будет выполнено в будущем, по завершению этого Task'а. Кроме того, вы можете дождаться о окончания Task'а асинхронно, используя await. Вы можете снаружи поймать и обработать исключение, произошедшее при выполнении Task'а. Всё это с Thread'ом недоступно.

вторник, 9 октября 2018 г.

CancellationToken: почему структура?

Почему CancellationToken реализован как структура? Ведь структура является типом значения, как тогда реализован данный механизм?
static void Main(string[] args) { CancellationTokenSource cancelTokenSource = new CancellationTokenSource(); CancellationToken token = cancelTokenSource.Token;
Task task1 = new Task(() => Factorial(5, token)); task1.Start();
cancelTokenSource.Cancel(); }
static void Factorial(int x, CancellationToken token) { int result = 1; for (int i = 1; i <= x; i++) { if (token.IsCancellationRequested) { Console.WriteLine("Операция прервана токеном"); return; }
result *= i; Console.WriteLine("Факториал числа {0} равен {1}", i, result); Thread.Sleep(5000); } }
Ведь структура является типом значения и создается копия объекта при передачи параметра CancellationToken token


Ответ

Почему CancellationToken реализован как структура?
Для борьбы за эффективность.
В большинстве случаев, да, CancellationToken вполне мог бы быть и классом, одна аллокация ничего не меняет, так как многопоточный код обычно некритичен к паре лишних мелких аллокаций.
Но ведь CancellationToken задумывался как общий механизм отмены. И существуют случаи, в которых аллокации критичны для пользователей. Если бы CancellationToken был классом, то в этих случаях пользователям фреймворка проходилось бы пользоваться самописной структурой, и, хуже того, они не смогли бы пользоваться библиотечными методами.
(Например, если пользователь хочет в метод, который ожидает CancellationToken, передать CancellationToken.None, потому что ему нужна скорость и не нужна отмена.)
Так что разработчики решили облегчить нам, пользователям, жизнь, и сделали CancellationToken таки структурой.

По поводу реализации механизма: да, разработчики нарушили семантику, и CancellationToken ведёт себя как типичный класс, а не как типичная структура. Они реализовали это таким образом. В CancellationToken есть единственное поле private CancellationTokenSource m_source; (ссылка на исходники). В проверке на равенство сравнивается m_source, и при копировании m_source копируется тоже, так что копия токена ведёт себя как оригинал, и тем самым неотличима от него. В частности, если оригинал токена отменён, то его копия — тоже.

понедельник, 8 октября 2018 г.

Настройка планировщика на подключение флешки

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


Ответ

Я слегка слукавил, без планировщика не обойтись, хотя, наверное, можно и через автозагрузку решить всё это.
Я для одного пожилого сотрудника когда-то использовал вот такой скрипт, который срабатывал, если вставлялась флэшка, автоопределяемая с буквой T и меткой "BP_flahka". Если буква диска не важна, то вместо if ($driveLetter -eq 'T:' -and $driveLabel -eq 'BP_flashka') можно использовать просто if ($driveLabel -eq 'ТутМеткаВашейФлэшки'). Код скрипта:
#Requires -version 2.0 Register-WmiEvent -Class win32_VolumeChangeEvent -SourceIdentifier volumeChange write-host (get-date -format s) " Beginning script..." do{ $newEvent = Wait-Event -SourceIdentifier volumeChange $eventType = $newEvent.SourceEventArgs.NewEvent.EventType $eventTypeName = switch($eventType) { 1 {"Configuration changed"} 2 {"Device arrival"} 3 {"Device removal"} 4 {"docking"} } write-host (get-date -format s) " Event detected = " $eventTypeName if ($eventType -eq 2) { $driveLetter = $newEvent.SourceEventArgs.NewEvent.DriveName $driveLabel = ([wmi]"Win32_LogicalDisk='$driveLetter'").VolumeName write-host (get-date -format s) " Drive name = " $driveLetter write-host (get-date -format s) " Drive label = " $driveLabel # Запустить, если буква диска и метка совпали с нужными значениями if ($driveLetter -eq 'T:' -and $driveLabel -eq 'BP_flashka') { write-host (get-date -format s) " Starting task in 3 seconds..." start-sleep -seconds 3 start-process "E:\sync.bat" } } Remove-Event -SourceIdentifier volumeChange } while (1-eq1) #Идём в цикле дальше Unregister-Event -SourceIdentifier volumeChange
Дальше всё просто. В планировщике создаём новое задание, с такими параметрами: Триггер: At log on Действие: Start a program Program/script: powershell Аргументы: -WindowStyle Hidden -ExecutionPolicy Unrestricted -File "Диск:\Путь\имя файла со скриптом.ps1"
Update 1.
Проверок для вставляемого хранилища можно устроить массу. Например:
# узнаем тип файловой системы флэшки $fileSystem = ([wmi]"Win32_LogicalDisk='$driveLetter'").FileSystem # проверяем, сколько свободного места осталось (в байтах) на флэшке $freeSpace = ([wmi]"Win32_LogicalDisk='$driveLetter'").FreeSpace # или общий размер всей флэшки: $size = ([wmi]"Win32_LogicalDisk='$driveLetter'").Size # и так далее, а затем просто проверяем нужный нам параметр, например: if ($fileSystem -eq 'NTFS') { # в этом случае бэкап можно делать одним большим файлом } # и так далее
Update 2. Работать будет, начиная с Windows Vista/Server 2008