1

In some cases in my program, I use a TaskCompletionSource to create a task and manipulate its state.
In other cases, the task is created and executed in a "regular" way.

In case of regular task creation and execution, the state of the task is "Running" while it's being executed. This "Running" state is checked at other places to ensure the task is being executed?

How can I set this state on the task when using the TaskCompletionSource? If I don't set the state to "Running", then the mentioned check fails.

Tobias Knauss
  • 3,361
  • 1
  • 21
  • 45
  • 2
    Question: *why* do you need to set it to running? I've been working with TCS for many many years with some very widely consumed libraries, and nobody has ever asked for this as a feature – Marc Gravell Aug 12 '21 at 16:03
  • Tasks are not threads. If you're writing code where you need to create, start, and stop Tasks is if they are threads, you're probably using them wrong. – Patrick Tucci Aug 12 '21 at 16:08
  • @PatrickTucci: I could use a thread, but I can also use a task for the work I have to do. In any case I need to know when the task is running, because the UI controls will be disabled when the task is started, and enabled when the task is finished. But the task is _not_ started by the UI, but by a library. – Tobias Knauss Aug 13 '21 at 05:01
  • @MarcGravell: maybe everybody has worked around that and created code in a different way because this functionality is missing. I think, that it belongs into TCS because it allows you to emulate the "normal" way how a task transitions between the states. A task that goes from "Created" to "RanToCompletion" without _having done any work_ seems wrong to me. It's possible though, and obviously sufficient in most cases, but I want to be able to detect a "Running" state. – Tobias Knauss Aug 13 '21 at 05:06
  • @TobiasKnauss I was not suggesting that you should use a thread instead. I was suggesting that you don't understand [Tasks and task-based asynchronous programming in C#](https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap) because you're talking about them as if they are the same or even tangentially related. They are not. – Patrick Tucci Aug 13 '21 at 11:07
  • @PatrickTucci: I know that Tasks and Threads are different things, and I think I also know the difference well enough. But I don't care whether a thread or a task is doing my work, I need it to run in the background and I need to know when it is running and when it is finished. – Tobias Knauss Aug 13 '21 at 11:39
  • 1
    @Tobias well, one of my TCS libraries has over 100M downloads, and my users are *not* quiet about their gripes, but... I guess maybe? – Marc Gravell Aug 13 '21 at 18:51

1 Answers1

1

The only way I have found to achieve that is via reflection. The following solution is tested and it's working.

private const string s_fieldName_StateFlags = "m_stateFlags";

/// <summary> From https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs </summary>
[Flags]
public enum TaskState
{
  TASK_STATE_STARTED                      = 0x10000,    //bin: 0000 0000 0000 0001 0000 0000 0000 0000
  TASK_STATE_DELEGATE_INVOKED             = 0x20000,    //bin: 0000 0000 0000 0010 0000 0000 0000 0000
  TASK_STATE_DISPOSED                     = 0x40000,    //bin: 0000 0000 0000 0100 0000 0000 0000 0000
  TASK_STATE_EXCEPTIONOBSERVEDBYPARENT    = 0x80000,    //bin: 0000 0000 0000 1000 0000 0000 0000 0000
  TASK_STATE_CANCELLATIONACKNOWLEDGED     = 0x100000,   //bin: 0000 0000 0001 0000 0000 0000 0000 0000
  TASK_STATE_FAULTED                      = 0x200000,   //bin: 0000 0000 0010 0000 0000 0000 0000 0000
  TASK_STATE_CANCELED                     = 0x400000,   //bin: 0000 0000 0100 0000 0000 0000 0000 0000
  TASK_STATE_WAITING_ON_CHILDREN          = 0x800000,   //bin: 0000 0000 1000 0000 0000 0000 0000 0000
  TASK_STATE_RAN_TO_COMPLETION            = 0x1000000,  //bin: 0000 0001 0000 0000 0000 0000 0000 0000
  TASK_STATE_WAITINGFORACTIVATION         = 0x2000000,  //bin: 0000 0010 0000 0000 0000 0000 0000 0000
  TASK_STATE_COMPLETION_RESERVED          = 0x4000000,  //bin: 0000 0100 0000 0000 0000 0000 0000 0000
  TASK_STATE_THREAD_WAS_ABORTED           = 0x8000000,  //bin: 0000 1000 0000 0000 0000 0000 0000 0000
  TASK_STATE_WAIT_COMPLETION_NOTIFICATION = 0x10000000, //bin: 0001 0000 0000 0000 0000 0000 0000 0000
  TASK_STATE_EXECUTIONCONTEXT_IS_NULL     = 0x20000000, //bin: 0010 0000 0000 0000 0000 0000 0000 0000
  TASK_STATE_TASKSCHEDULED_WAS_FIRED      = 0x40000000, //bin: 0100 0000 0000 0000 0000 0000 0000 0000
}

public  static void SetTaskRunning<TResult> (TaskCompletionSource<TResult> i_taskCompletionSource)
{
  if (i_taskCompletionSource == null)
    throw new ArgumentNullException (nameof (i_taskCompletionSource));

  var value = TaskState.TASK_STATE_STARTED
           |  TaskState.TASK_STATE_DELEGATE_INVOKED;

  var task      = i_taskCompletionSource.Task;
  var type      = task.GetType ();
  var fieldInfo = type.GetField (s_fieldName_StateFlags, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
  fieldInfo.SetValue (task, (int)value);
}
Tobias Knauss
  • 3,361
  • 1
  • 21
  • 45
  • Just out of curiosity: why do you prefix the `SetTaskRunning`'s parameter with `i_`? – Peter Csala Aug 13 '21 at 07:35
  • BTW If you stick with this approach an extension method might make the usage more convenient in my opinion. – Peter Csala Aug 13 '21 at 07:36
  • 1
    @PeterCsala: based on our convention, we prefix all input parameters of methods with "i_" and out parameters with "o_". I just copied it here. I intentionally did not make this an extension method, because it should be used carefully and not like the built-in methods `SetResult` etc. – Tobias Knauss Aug 13 '21 at 11:41