Я работаю над многозадачным сетевым проектом, и я новичок 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()
?
c#
lambda
task-parallel-library
task
MFatihMAR
источник
источник
rawData
- это сетевой пакет данных, который имеет контейнерный класс (например, DataPacket), и я повторно использую этот экземпляр, чтобы уменьшить давление GC. Итак, если я используюrawData
напрямуюTask
, его можно (вероятно) изменить до того, какTask
обработать его. Теперь, думаю, я могу создатьbyte[]
для него еще один экземпляр. Думаю, для меня это самое простое решение.Action<byte[]>
не меняет этого.Ответы:
private void RunAsync() { string param = "Hi"; Task.Run(() => MethodWithParameter(param)); } private void MethodWithParameter(string param) { //Do stuff }
редактировать
По многочисленным просьбам я должен отметить, что
Task
запущенный будет работать параллельно с вызывающим потоком. Предполагая, что по умолчаниюTaskScheduler
будет использоваться .NETThreadPool
. В любом случае, это означает, что вам необходимо учитывать любой передаваемый параметр (ы)Task
как потенциально доступный для нескольких потоков одновременно, делая их общим состоянием. Это включает доступ к ним в вызывающем потоке.В моем приведенном выше коде этот случай совершенно спорный. Строки неизменны. Вот почему я использовал их в качестве примера. Но скажите, что вы не используете
String
...Одно из решений - использовать
async
иawait
. Это, по умолчанию, захватываетSynchronizationContext
вызывающий поток и создает продолжение для остальной части метода после вызоваawait
и присоединяет его к созданномуTask
. Если этот метод выполняется в потоке графического интерфейса WinForms, он будет типаWindowsFormsSynchronizationContext
.Продолжение будет запущено после отправки обратно в захваченное
SynchronizationContext
- опять же только по умолчанию. Так что послеawait
звонка вы вернетесь к теме, с которой начали . Вы можете изменить это разными способами, в частности, используяConfigureAwait
. Короче говоря, остальная часть этого метода не будет продолжаться до тех пор , послеTask
завершения другого потока. Но вызывающий поток будет продолжать работать параллельно, а не остальная часть метода.Это ожидание завершения выполнения остальной части метода может быть или нежелательно. Если в дальнейшем ничто в этом методе не обращается к параметрам, переданным в, возможно,
Task
вы вообще не захотите использоватьawait
.Или, может быть, вы используете эти параметры намного позже в методе. Нет причин
await
сразу же, так как вы можете спокойно продолжать работу. Помните, что вы можете сохранитьTask
возвращаемое значение в переменной, аawait
позже - даже в том же методе. Например, когда вам нужно безопасно получить доступ к переданным параметрам после выполнения кучи другой работы. Опять же, вам не нужно делатьawait
этоTask
справа при запуске.В любом случае, простой способ сделать это потокобезопасным по отношению к переданным параметрам
Task.Run
- это сделать следующее:Вы должны сначала украсить
RunAsync
сasync
:private async void RunAsync()
Важная заметка
Желательно, чтобы отмеченный метод не возвращал void, как упоминается в связанной документации. Распространенным исключением из этого правила являются обработчики событий, такие как нажатие кнопки и т.п. Они должны вернуться в пустоту. В противном случае я всегда стараюсь вернуть или при использовании . Это хорошая практика по нескольким причинам.
async
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
выполняете задачу, вы можете избежать обработки переданных параметров как потенциально разделяемого ресурса со всеми подводными камнями изменения чего-либо сразу из нескольких потоков. Также остерегайтесь закрытия . Я не буду описывать их подробно, но связанная статья отлично справляется с этим.Примечание
Немного не по теме, но будьте осторожны при использовании любого типа «блокировки» потока графического интерфейса WinForms из-за того, что он помечен значком
[STAThread]
. Использованиеawait
не будет блокировать вообще, но иногда я вижу, что оно используется в сочетании с какой-то блокировкой.«Блокировать» заключено в кавычки, потому что вы технически не можете заблокировать поток графического интерфейса WinForms . Да, если вы используете
lock
поток графического интерфейса WinForms, он все равно будет перекачивать сообщения, несмотря на то, что вы думаете, что он «заблокирован». Это не.В очень редких случаях это может вызвать странные проблемы. Одна из причин, по которой вы никогда не захотите использовать
lock
, например, при рисовании. Но это крайний и сложный случай; однако я видел, как это вызывает сумасшедшие проблемы. Поэтому я отметил это для полноты картины.источник
Task.Run(() => MethodWithParameter(param));
. Это означает , что еслиparam
модифицируется послеTask.Run
, вы могли бы иметь неожиданные результаты наMethodWithParameter
.Используйте захват переменных для «передачи» параметров.
var x = rawData; Task.Run(() => { // Do something with 'x' });
Вы также можете использовать
rawData
напрямую, но вы должны быть осторожны, если вы измените значениеrawData
вне задачи (например, итератор вfor
цикле), это также изменит значение внутри задачи.источник
Task.Run
.Теперь вы также можете:
Action<int> action = (o) => Thread.Sleep(o); int param = 10; await new TaskFactory().StartNew(action, param)
источник
IDisposable
объект делегату задачи, чтобы разрешить предупреждение ReSharper «Захваченная переменная расположена во внешней области» , это очень удобно. Вопреки распространенному мнению, нет ничего плохого в использованииTask.Factory.StartNew
вместо того,Task.Run
где вам нужно передать состояние. Смотрите здесь .Я знаю, что это старый поток, но я хотел поделиться решением, которое мне пришлось использовать, поскольку в принятом сообщении все еще есть проблема.
Проблема:
Как указал Александр Северино, если
param
(в функции ниже) изменится вскоре после вызова функции, вы можете получить неожиданное поведение вMethodWithParameter
.Мое решение:
Чтобы учесть это, я написал что-то вроде следующей строки кода:
(new Func<T, Task>(async (p) => await Task.Run(() => MethodWithParam(p)))).Invoke(param);
Это позволило мне безопасно использовать параметр асинхронно, несмотря на то, что параметр изменился очень быстро после запуска задачи (что вызвало проблемы с опубликованным решением).
Используя этот подход,
param
(тип значения) получает свое значение, передаваемое, поэтому даже если метод async запускается послеparam
изменений, онp
будет иметь то же значение,param
что и при выполнении этой строки кода.источник
var localParam = param; await Task.Run(() => MethodWithParam(localParam));
Просто используйте 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; }
источник
for(int rawData = 0; rawData < 10; ++rawData) { Task.Run(() => { Console.WriteLine(rawData); } ) }
это не будет вести себя так, как если быrawData
было передано, как в примере OP StartNew.Неясно, была ли исходная проблема той же проблемой, что и у меня: желание максимизировать потоки ЦП при вычислениях внутри цикла с сохранением значения итератора и сохранением встроенного, чтобы избежать передачи тонны переменных в рабочую функцию.
for (int i = 0; i < 300; i++) { Task.Run(() => { var x = ComputeStuff(datavector, i); // value of i was incorrect var y = ComputeMoreStuff(x); // ... }); }
Я заставил это работать, изменив внешний итератор и локализовав его значение с помощью ворот.
for (int ii = 0; ii < 300; ii++) { System.Threading.CountdownEvent handoff = new System.Threading.CountdownEvent(1); Task.Run(() => { int i = ii; handoff.Signal(); var x = ComputeStuff(datavector, i); var y = ComputeMoreStuff(x); // ... }); handoff.Wait(); }
источник
Идея состоит в том, чтобы избегать использования сигнала, подобного приведенному выше. Накачка значений типа int в структуру предотвращает изменение этих значений (в структуре). У меня была следующая проблема: переменная цикла, которую я должен был изменить до вызова DoSomething (i) (значение i было увеличено в конце цикла до вызова () => DoSomething (i, i i)). Со структурами этого больше не происходит. Неприятная ошибка, которую нужно найти: DoSomething (i, i i) выглядит великолепно, но никогда не уверен, будет ли он вызван каждый раз с другим значением для i (или просто 100 раз с i = 100), следовательно -> struct
struct Job { public int P1; public int P2; } … for (int i = 0; i < 100; i++) { var job = new Job { P1 = i, P2 = i * i}; // structs immutable... Task.Run(() => DoSomething(job)); }
источник