2015-04-23 3 views
5

У меня есть метод async после завершения которого я хочу запустить другой метод. Это отлично работает, если я просто вызываю метод и добавляю .ContinueWith()Создать хорошую задачу холода

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

Я хотел бы построить задачу, попытаться добавить его, а затем запустить задачу

Тем не менее, кажется, что Task.Start() немедленно завершает задание, вызывающее действие продолжать работать и все ждет .. не ждать.

может кто-нибудь объяснить, почему это происходит, и правильный путь для достижения моей цели?

namespace UnitTestProject2 
{ 
    [TestClass] 
    public class taskProblem 
    { 
     [TestMethod] 
     public void Test() 
     { 
      CancellationTokenSource cancel = new CancellationTokenSource(); 
      ConcurrentDictionary<Guid, Task> tasks = new ConcurrentDictionary<Guid,Task>(); 
      Guid id = Guid.NewGuid(); 
      Task t = new Task(async() => await Get(), cancel.Token); 
      t.ContinueWith(Complete); 
      if (tasks.TryAdd(id, t)) 
      { 
       t.Start(); 
      } 
      else 
      { 
       //another thread is stopping stuff dont start new tasks 
      } 

      t.Wait(); //expected to wait for the get function to complete 
      Console.WriteLine("end test"); 
     } 

     public async Task Get() 
     { 
      Console.WriteLine("start task"); 
      await Task.Delay(10000); 
      Console.WriteLine("end task"); 
     } 

     public void Complete(Task t) 
     { 
      Console.WriteLine("Complete"); 
     } 
    } 
} 

выход:

start task 
end test 
Complete 

ожидается выход:

start task 
end task 
Complete 
end test 

Update: Оказывается, нет никакого способа, чтобы создать новую задачу, которая не будет немедленно начать или полностью немедленно на Task.Start?

+1

В чем проблема, которую вы пытаетесь решить?Требование не имеет смысла и обертывания Задачи, которые два уровня глубоко не помогают. Существует * никогда * хорошая причина для создания холодных задач и вызова 'Start'. Более того, нет причин для переноса «Get», который уже возвращает запущенную задачу внутри другой. Просто напишите 'Task t = Get();'. Что касается требования, это не имеет смысла. Вы всегда можете добавить новый элемент в параллельный словарь, поскольку на самом деле вы создаете для него новый ключ Guid. –

+1

Похоже, вы передаете делегат async в конструктор 'Task', который не будет ждать завершения задачи, возвращаемой из' Get'. Почему вы пытаетесь связать вызов 'Get' в другой задаче? – Lee

+0

настоящее приложение отправляет push-уведомления из очереди и имеет метод Stop() (вызванный из другого потока), чтобы остановить запуск новых задач. В этом случае мне нужна незапущенная задача, поэтому я могу добавить ее в параллельный словарь по потоковому безопасному пути. очевидно, есть другие способы сделать это, которые я буду вынужден использовать, если я не могу это понять. – Ewan

ответ

6

Ваш делегат не имеет асинхронности. async void-methods - это огонь и забудьте.

См первую точку Резюме Шаблоны и антишаблоны: http://rarcher.azurewebsites.net/Post/PostContent/31

Может быть, вы можете сделать что-то подобное:

[TestFixture] 
public class FIXTURENAMETests { 
    [Test] 
    public async Task NAME() { 
    var tcs = new TaskCompletionSource<bool>(); 
    Task t = LongRunningStuff(tcs); 

    if (CanInsertInDictionary(t)) { 
     tcs.SetResult(true); 
    } else { 
     tcs.SetException(new Exception()); 
    } 

    Trace.WriteLine("waiting for end"); 

    try { 
     await t; 
    } 
    catch (Exception exception) { 
     Trace.WriteLine(exception); 
    } 

    Trace.WriteLine("end all"); 
    } 

    private bool CanInsertInDictionary(Task task) { 
    return true; 
    } 

    private async Task LongRunningStuff(TaskCompletionSource<bool> tcs) { 
    Trace.WriteLine("start"); 
    try { 
     await tcs.Task; 
    } 
    catch (Exception) { 
     return; 
    } 
    Trace.WriteLine("do long running stuff"); 
    await Task.Delay(10000); 
    Trace.WriteLine("end"); 
    } 
} 
+0

Ahh! я понял, что это должно быть что-то вроде анонса. но у вас есть решение? – Ewan

+0

Я отредактировал ответ. возможно, вы можете сделать что-то подобное. –

+0

Хммм, еще одна возможная работа вокруг, но не существует способа создать незапланированную задачу? действительно хочу сделать Task t = Get(). DontStartYet() или что-то – Ewan

-1

Во-первых, ContinueWith будет возвращать новый Task, вы хотите, чтобы ждать, пока метод Complete завершается, но вы ожидаете первой задачи t.

Таким образом, для вывода Complete перед end test, вы должны ждать на второй задачи:

Task t = new Task(async() => await Get(), cancel.Token); 

// NOTE: t2 is a new Task returned from ContinueWith 
Task t2 = t.ContinueWith(Complete); 

if (tasks.TryAdd(id, t)) 
{ 
    t.Start(); 
} 
else 
{ 
} 

// NOTE: Waiting on t2, NOT t 
t2.Wait(); 

Console.WriteLine("end test"); 

Теперь выход будет:

start task 
Complete 
end test 
end task 

Ладно, это еще не ожидаемый результат , end task следует напечатать до Complete. Это связано с тем, что ваше асинхронное действие не является ожидаемым: How to await on async delegate

Не знаю, правильно ли я понял ваши требования. Если это мне, я мог бы сделать это следующим образом:

Добавить новый опорный класс:

public class TaskEntry 
{ 
    public Task Task { get; set; } 
} 

Затем изменить свой код:

Guid id = Guid.NewGuid(); 

Task task = null; 
var entry = new TaskEntry(); 
if (tasks.TryAdd(id, entry)) 
{ 
    entry.Task = Get(); 

    // Notice this line of code: 
    task = entry.Task.ContinueWith(Complete); 
} 

if (task != null) 
{ 
    task.Wait(); 
} 

Console.WriteLine("end test"); 

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

+0

Вы правы. Я должен проверить выполнение задачи продолжения, чтобы обеспечить ожидаемый результат. но реальной проблемой является асинхронный делегат. Ваш дополнительный класс TaskEntry - хорошая работа. Но вы говорите, что нет способа создать ожидаемую задачу? – Ewan

Смежные вопросы