Подтвердить что ты не робот

Задание. Запуск с параметрами (-ами)?

Я работаю над многозадачным сетевым проектом, и я новичок в Threading.Tasks. Я реализовал простой Task.Factory.StartNew(), и мне интересно, как я могу это сделать с помощью Task.Run()?

Вот базовый код:

Task.Factory.StartNew(new Action<object>(
(x) =>
{
    // Do something with 'x'
}), rawData);

Я просмотрел System.Threading.Tasks.Task в обозревателе объектов, и я не смог найти параметр Action<T>. Существует только Action, который принимает параметр void и не имеет типа.

Есть только 2 вещи, похожие: static Task Run(Action action) и static Task Run(Func<Task> function), но не могут публиковать параметры с обоими.

Да, я знаю, что могу создать для него простой метод расширения, но мой главный вопрос: можем ли мы записать его на одной строке с помощью Task.Run()?

4b9b3361

Ответ 1

private void RunAsync()
{
    string param = "Hi";
    Task.Run(() => MethodWithParameter(param));
}

private void MethodWithParameter(string param)
{
    //Do stuff
}

Edit

Из-за большого спроса я должен отметить, что запущенный Task будет работать параллельно с вызывающим потоком. Предполагая, что по умолчанию TaskScheduler это будет использовать .NET ThreadPool. В любом случае, это означает, что вам нужно учитывать любые параметры, передаваемые в Task, как потенциально доступные несколькими потоками одновременно, что делает их общим. Это включает в себя доступ к ним в вызывающем потоке.

В моем вышеприведенном коде этот случай сделан полностью спорным. Строки неизменяемы. Вот почему я использовал их в качестве примера. Но скажите, что вы не используете String...

Одним из решений является использование async и await. Это по умолчанию будет захватывать SynchronizationContext вызывающего потока и будет создавать продолжение для остальной части метода после вызова await и прикрепить его к созданному Task. Если этот метод работает в потоке GUI WinForms, он будет иметь тип WindowsFormsSynchronizationContext.

Продолжение будет выполняться после того, как оно будет отправлено обратно в захваченный SynchronizationContext - снова только по умолчанию. Таким образом, вы вернетесь к потоку, который вы начали после вызова await. Вы можете изменить это различными способами, особенно используя ConfigureAwait. Короче говоря, остальная часть этого метода не будет продолжаться до тех пор, пока после Task не завершится в другом потоке. Но вызывающий поток будет продолжать работать параллельно, но не весь метод.

Ожидание завершения остальной части метода может быть нежелательным. Если ничто в этом методе не получит доступ к параметрам, переданным в Task, вы можете вообще не использовать await.

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

В любом случае простой способ сделать этот поток безопасным по отношению к параметрам, переданным в Task.Run, состоит в том, чтобы сделать это:

Вы должны сначала украсить RunAsync async:

private async void RunAsync()

Важное примечание

Предпочтительно метод, отмеченный async , не должен возвращать void, как упоминается связанная документация. Общим исключением для этого являются обработчики событий, такие как нажатия кнопок и т.д. Они должны вернуть пустоту. В противном случае я всегда пытаюсь вернуть Task или Task<TResult> при использовании async. Это хорошая практика по нескольким причинам.

Теперь вы можете await запустить Task, как показано ниже. Вы не можете использовать await без async.

await Task.Run(() => MethodWithParameter(param));
//Code here and below in the same method will not run until AFTER the above task has completed in one fashion or another

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

Боковое примечание

Отключить тему, но будьте осторожны с использованием любого типа "блокировки" в потоке GUI WinForms из-за того, что он помечен [STAThread]. Использование await вообще не будет блокировать, но иногда я вижу, что оно используется в сочетании с какой-то блокировкой.

"Блок" находится в кавычках, потому что вы технически не можете заблокировать поток графического интерфейса WinForms. Да, если вы используете lock в потоке GUI WinForms, он все равно будет накачивать сообщения, несмотря на то, что вы думаете, что это "заблокировано". Это не так.

Это может вызвать странные проблемы в очень редких случаях. Одна из причин, по которой вы никогда не хотите использовать lock при рисовании, например. Но это крайний и сложный случай; однако я видел, что это вызывает сумасшедшие проблемы. Поэтому я отметил это ради полноты.

Ответ 2

Использовать захват переменной для параметров "pass in".

var x = rawData;
Task.Run(() =>
{
    // Do something with 'x'
});

Вы также можете использовать rawData напрямую, но вы должны быть осторожны, если вы измените значение rawData вне задачи (например, итератор в цикле for), оно также изменит значение внутри задача.

Ответ 3

Просто используйте Task.Run

var task = Task.Run(() =>
{
    //this will already share scope with rawData, no need to use a placeholder
});

Или, если вы хотите использовать его в методе и ждать задачи позже

public Task<T> SomethingAsync<T>()
{
    var task = Task.Run(() =>
    {
        //presumably do something which takes a few ms here
        //this will share scope with any passed parameters in the method
        return default(T);
    });

    return task;
}

Ответ 4

Теперь вы также можете:

Action<int> action = (o) => Thread.Sleep(o);
int param = 10;
await new TaskFactory().StartNew(action, param)

Ответ 5

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

Проблема:

Как отметил Alexandre Severino, если param (в приведенной ниже функции) вскоре после вызова функции произойдет неожиданное поведение в MethodWithParameter.

Task.Run(() => MethodWithParameter(param)); 

Мое решение:

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

(new Func<T, Task>(async (p) => await Task.Run(() => MethodWithParam(p)))).Invoke(param);

Это позволило мне безопасно использовать параметр асинхронно, несмотря на то, что после запуска задачи параметр очень быстро изменился (что вызвало проблемы с размещенным решением).

Используя этот подход, param (тип значения) получает свое значение, переданное, поэтому, даже если метод async запускается после изменений param, p будет иметь любое значение param, когда эта строка кода была запущена.