I've got a program that handles a variety of tasks running in parallel. A single task acts as a manager of sorts, making sure certain conditions are met before the next task is ran. However, I've found that sometimes a task will sit in the WaitingToRun state for a very long time. Here's the following code:
mIsDisposed = false;
mTasks = new BlockingCollection<TaskWrapper>(new ConcurrentQueue<TaskWrapper>());
Task.Factory.StartNew(() => {
while (!mIsDisposed) {
var tTask = mTasks.Take();
tTask.task.Start();
while (tTask.task.Status == TaskStatus.WaitingToRun) {
Console.WriteLine("Waiting to run... {0}", tTask.task.Id);
Thread.Sleep(200);
}
tTask.ready.Wait();
}
mTasks.Dispose();
});
DoWork();
DoWork();
DoWork();
DoWork();
DoWorkAsync();
DoWorkAsync();
DoWorkAsync();
DoWorkAsync();
DoWorkAsync();
DoWork();
TaskWrapper is very simply defined as:
private class TaskWrapper
{
public Task task { get; set; }
public Task ready { get; set; }
}
And tasks are only currently added in 2 places:
public void DoWork()
{
DoWorkAsync().Wait();
}
public Task DoWorkAsync()
{
ManualResetEvent next = new ManualResetEvent(false);
Task task = new Task(() => ActualWork(next));
Task ready = Task.Factory.StartNew(() => next.Wait());
mTasks.Add(new TaskWrapper() {
task = task,
ready = ready
});
return task;
}
Where ActualWork(next) calls next.Set().
This queues work and waits until next has been set before allowing the next work item to proceed. You can either wait for the entire task to finish before continuing by calling DoWork() or queue multiple tasks at once (which are supposed to run after next has been set).
However, when adding a task via DoWorkAsync(), after calling tTask.task.Start(), tTask.task sits in the WaitingToRun state for a loooong time (like 30 seconds to a minute), then magically starts running. I've monitored this using the while loop, and Waiting To Run... # will display for quite some time.
Calling DoWork() always runs immediately. I'm sure this has something to do with calling Wait on the task that is set to run.
I'm at a loss, here.
UPDATE:
I've managed to make the code work, but I'd still like to know why there's an issue in the first place.
After some experimental changes, I've managed to fix my own problem, but it's more of a "Oh, so I just can't do that" rather than a good fix. It turns out my problem was enqueuing tasks to run too quickly. By modifying DoWorkAsync() to no longer use Task.Factory.StartNew and changing tTask.ready.Wait() to tTask.ready.RunSynchronously I've managed to solve my issue.
Is there a reason the TaskScheduler is delaying the scheduling of my tasks? Am I saturating some underlying resources? What's going on here?
The threads will be run in the system's thread pool. The thread pool has a minimum number of threads available at all times (see ThreadPool.SetMinThreads()). If you try to create more than that many threads, a delay of approximately 500ms will be introduced between each new thread starting.
There is also a maximum number of threads in the thread pools (see ThreadPool.GetMaxThreads()), and if you reach that limit no new threads will be created; it will wait until an old thread dies before scheduling a new one (or rather, rescheduling the old one to run your new thread, of course).
You are unlikely to be hitting that limit though - it's probably over 1000.
Ok, I've just been faced with a similar issue. A bit of code that created and started a task ran, but the task never started (it just changed status to WaitingToRun)
Having tried the other options in this thread to no avail I thought about it a bit more, and realised that the code that was calling this method was itself called in a continuation task, that had been specified to run on the UI task scheduler (As it needed to update the UI)...
So something like
void Main()
{
var t1 = new Task(() => Console.WriteLine("hello, I'm task t1"));
t1.ContinueWith(t => CreateAndRunASubTask(), TaskScheduler.FromCurrentSynchronizationContext());
t1.Start();
Console.WriteLine("All tasks done with");
}
// Define other methods and classes here
public void CreateAndRunASubTask()
{
var tsk = new Task(() => Console.WriteLine("hello, I'm the sub-task"));
tsk.Start();
Console.WriteLine("sub-task has been told to start");
tsk.Wait();
// the code blocks on tsk.Wait() indefinately, the tsk status being "WaitingToRun"
Console.WriteLine("sub-task has finished");
}
The fix turned out to be pretty simple - when specifying the continuation task you need to specify the TaskContinuationOption: TaskContinuationOptions.HideScheduler
This has the effect of... (taken from the XML comment)
Specifies that tasks created by the continuation by calling methods
such as System.Threading.Tasks.Task.Run(System.Action) or
System.Threading.Tasks.Task.ContinueWith(System.Action{System.Threading.Tasks.Task})
see the default scheduler (System.Threading.Tasks.TaskScheduler.Default) rather
than the scheduler on which this continuation is running as the current scheduler.
ie (in my example)
t1.ContinueWith(t =>
CreateAndRunASubTask(),
System.Threading.CancellationToken.None,
TaskContinuationOptions.HideScheduler,
TaskScheduler.FromCurrentSynchronizationContext());
Hope this helps someone, as it stumped me for a good while!
Just faced similar issue.
I have a bunch of similar tasks running inifite loops, one of that tasks from time to time stays in WaitingToRun state permamently.
Creating tasks in that way did the trick for me:
_task = new Task(() => DoSmth(_cancellationTokenSource.Token), TaskCreationOptions.LongRunning);
_task.Start();
Related
I am running a Thread which get called in a static interval.
In this Thread I am running several Tasks (20-200).
All this works fine, but when the Thread gets called the first time, it takes like ~1 sec for one Tasks to start.
As soon as the while loop is in the second loop or when the Thread stops, and gets called a second time, the problem is gone.
public static async void UpdateThread()
{
while(!stop)
{
foreach (DSDevice device in DSDevices)
{
var task = Task.Run(() =>
{
// Delay is measured here
// Do Stuff
});
}
//No Delay
await Task.WhenAll(tasks);
Thread.Sleep(Sleeptime);
}
}
The Task.Run runs the code on the ThreadPool, and the ThreadPool creates initially a limited number of threads on demand. You can increase this limit with the SetMinThreads method:
ThreadPool.SetMinThreads(200, 200);
...but check out the documentation before doing so. Increasing this threshold is not something that you should do without thinking. Having too many ThreadPool threads defeats the purpose of having a pool in the first place. Think whether it's better to have a dedicated thread per device, for the whole life-time of the program.
As a side note, if I was in your shoes I would not parallelize the processing of the devices by creating tasks manually. I would use the Parallel.ForEach method, which exists for exactly this kind of job. As a bonus it allows to control the degree of parallelism, either to a specific number or to -1 for unlimited parallelism:
public static async Task MonitorDevicesPeriodicAsync(
CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
Task delayTask = Task.Delay(MonitorDevicesPeriodMilliseconds);
await Task.Run(() =>
{
ParallelOptions options = new() { MaxDegreeOfParallelism = -1 };
Parallel.ForEach(DSDevices, options, device =>
{
// Do Stuff with device
});
});
await delayTask;
}
}
The Parallel.ForEach invokes also the delegate on the ThreadPool (by default), and it can saturate it as easily as the await Task.WhenAll(tasks) approach, so you might need to use the ThreadPool.SetMinThreads method as well.
Three more off topic suggestions: prefer async Task over async void. Async void is intended for event handler only. Also use a CancellationToken for stopping the while loop instead of a non-volatile bool stop field. In a multithreaded environment, it's not guaranteed that the mutation of the field from one thread will be visible from other threads. Alternatively declare the field as volatile. Finally use the Task.Delay instead of the Thread.Sleep, create the Task.Delay task at the start of the iteration and await it at the end, for a stable periodic invocation.
I am new to TPL and I am wondering: How does the asynchronous programming support that is new to C# 5.0 (via the new async and await keywords) relate to the creation of threads?
Specifically, does the use of async/await create a new thread each time that they are used? And if there many nested methods that use async/await, is a new thread created for each of those methods?
In short NO
From Asynchronous Programming with Async and Await : Threads
The async and await keywords don't cause additional threads to be
created. Async methods don't require multithreading because an async
method doesn't run on its own thread. The method runs on the current
synchronization context and uses time on the thread only when the
method is active. You can use Task.Run to move CPU-bound work to a
background thread, but a background thread doesn't help with a process
that's just waiting for results to become available.
According to MSDN : async keyword
An async method runs synchronously until it reaches its first await expression, at which point the method is suspended until the awaited task is complete. In the meantime, control returns to the caller of the method, as the example in the next section shows.
Here is a sample code to check it :
class Program
{
static void Main(string[] args)
{
Program p = new Program();
p.Run();
}
private void Print(string txt)
{
string dateStr = DateTime.Now.ToString("HH:mm:ss.fff");
Console.WriteLine($"{dateStr} Thread #{Thread.CurrentThread.ManagedThreadId}\t{txt}");
}
private void Run()
{
Print("Program Start");
Experiment().Wait();
Print("Program End. Press any key to quit");
Console.Read();
}
private async Task Experiment()
{
Print("Experiment code is synchronous before await");
await Task.Delay(500);
Print("Experiment code is asynchronous after first await");
}
}
And the result :
We see the code of Experiment() method after await executes on another Thread.
But if I replace the Task.Delay by my own code (method SomethingElse) :
class Program
{
static void Main(string[] args)
{
Program p = new Program();
p.Run();
}
private void Print(string txt)
{
string dateStr = DateTime.Now.ToString("HH:mm:ss.fff");
Console.WriteLine($"{dateStr} Thread #{Thread.CurrentThread.ManagedThreadId}\t{txt}");
}
private void Run()
{
Print("Program Start");
Experiment().Wait();
Print("Program End. Press any key to quit");
Console.Read();
}
private async Task Experiment()
{
Print("Experiment code is synchronous before await");
await SomethingElse();
Print("Experiment code is asynchronous after first await");
}
private Task SomethingElse()
{
Print("Experiment code is asynchronous after first await");
Thread.Sleep(500);
return (Task.CompletedTask);
}
}
I notice the thread remains the same !
In conclusion, I'll say async/await code could use another thread, but only if the thread is created by another code, not by async/await.
In this case, I think Task.Delay created the thread, so I can conclude async/await does not create a new Thread like said by #Adriaan Stander.
Sorry for being late to the party.
I am new to TPL and I am wondering: How does the asynchronous
programming support that is new to C# 5.0 (via the new async and await
keywords) relate to the creation of threads?
async/await is not introduced for thread creation, but to utilize the current thread optimally.
Your app might read files, wait for response from another server or even do a computation with high memory access (Simply any IO task). These tasks are not CPU intensive (Any task that will not use 100% of your thread).
Think about the case when you are processing 1000 non CPU intensive tasks. In this case, process of creating 1000s of OS level thread might eat up more CPU and Memory than doing actual work on a single thread (4mb per thread in Windows, 4MB * 1000 = 4GB). At the same time if you run all the tasks sequentially, you might have to wait until the IO tasks gets finished. Which end up in long time to complete the task, while keeping the CPU idle.
Since we require parallelism to complete multiple tasks quickly, at the same time all parallel tasks are not CPU hungry, but creating threads is inefficient.
The compiler will break the execution at any method call to an async method (which gets called with an await) and immediately execute the code outside of the current code branch, once an await is reached, the execution will go inside the previous async. This will be repeated again and again until all the async calls are completed and their awaiters are satisfied.
If any of the async method have heavy CPU load without a call to an async method, then yes, your system will become unresponsive and all the remaining async methods will not get called until the current task is finished.
So I've been reading up on the threading model, and Async / Await can certainly lead to new threads being used (not necessarily created - the pool creates them at application start). It's up to the scheduler to determine if a new thread is needed. And as I see it, a call to an awaitable function may have internal details that increase the chances of the scheduler utilizing another thread; simply because more work means more opportunities / reasons for the scheduler to divvy out work.
WinRT async operations automatically happen on the thread pool. And typically you will be calling FROM the thread pool, except for UI thread work .. Xaml/Input/Events.
Async operations started on Xaml/UI threads have their results delivered back to the [calling] UI thread. But asynchronous operation results started from a thread pool thread are delivered wherever the completion happens, which may not be the same thread you were on before. The reason behind this is that code written for the thread pool is likely to be written to be thread safe and it is also for efficiency, Windows doesn't have to negotiate that thread switch.
So again, in answer to the OP, new threads are not necessarily created but your application can and will use multiple threads to complete asynchronous work.
I know this seems to contradict some of the literature regarding async / await, but that's because although the async / await construct is not by itself multithreaded. Awaitables are the, or one of the mechanisms by which the scheduler can divide work and construct calls across threads.
This is at the limit of my knowledge right now regarding async and threading, so I might not have it exactly right, but I do think it's important to see the relationship between awaitables and threading.
Using Async/Await doesn't necessarily cause a new thread to be created. But the use of Async/Await can lead to a new thread to be created because the awaitable function may internally spawn a new thread. And it often does, making the statement 'No, it doesn't spawn threads' almost useless in practice. For example, the following code spawns new threads.
VisualProcessor.Ctor()
{
...
BuildAsync();
}
async void BuildAsync()
{
...
TextureArray dudeTextures = await TextureArray.FromFilesAsync(…);
}
public static async Task<TextureArray> FromFilesAsync(...)
{
Debug.WriteLine("TextureArray.FromFilesAsync() T1 : Thread Id = " + GetCurrentThreadId());
List<StorageFile> files = new List<StorageFile>();
foreach (string path in paths)
{
if (path != null)
files.Add(await Package.Current.InstalledLocation.GetFileAsync(path)); // << new threads
else
files.Add(null);
}
Debug.WriteLine("TextureArray.FromFilesAsync() T2 : Thread Id = " + GetCurrentThreadId());
...
}
In case of Java Spring Framework, a method annotated with #Async runs in a separate thread. Quoting from official guide (https://spring.io/guides/gs/async-method) -
The findUser method is flagged with Spring’s #Async annotation,
indicating that it should run on a separate thread. The method’s
return type is CompletableFuture instead of User, a requirement
for any asynchronous service.
Of course in the backend it uses a Thread Pool and a Queue (where async tasks wait for a thread to be back in the pool).
In my program i have ~40 running task, defined like:
private void StartTryReconnectTask() {
TryReconnectCTKS = new CancellationTokenSource();
TryReconnectTask = new Task(this.TryReconnect, TryReconnectCTKS.Token);
TryReconnectTask.Start();
}
Inside TryReconnect() there is an infinite while loop that stops only when the task is cancelled. everything seems fine to me here.
Then i need to start a task (not infinite) on a button click:
private void ExecuteRepairCommand(object o) {
Task.Run(() => {
...
});
}
it take ~30/40 seconds to start this new task.
if i use thread everything works correctly, the thread starts instantly. why? what's the cause?
By default tasks are scheduled to ThreadPool. ThreadPool won't create new threads when you schedule lot of tasks. It will wait for sometime before creating new threads(based on some heuristics). That's why you notice a delay in starting of your tasks. I've explained it earlier here.
Back to your question. If your task is long running, you should really consider using LongRunning flag. It will instruct the Task Scheduler to give it a new thread; so your task can run independently for a long time without affecting other tasks.
Task.Factory.StartNew(() =>
{
...
}, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);
I have a Windows Service that reads from multiple MessageQueue instances. Those messagequeues all run their own Task for reading messages. Normally, after reading a message, the work of an I/O database is done. I've found articles claiming it's a good idea to use async on I/O operations, because it would free up threads. I'm trying to simulate the performance boost of using async I/O opertations in a Console application.
The Console application
In my test environment, I have 10 queues. GetQueues() returns 10 different MessageQueue instances.
static void Main(string[] args)
{
var isAsync = Console.ReadLine() == "Y";
foreach (var queue in queueManager.GetQueues())
{
var temp = queue;
Task.Run(() => ReceiveMessagesForQueue(temp, isAsync));
}
while (true)
{
FillAllQueuesWithMessages();
ResetAndStartStopWatch();
while(!AllMessagesRead())
{
Thread.Sleep(10);
}
Console.WriteLine("All messages read in {0}ms", stopWatch.ElapsedMilliseconds);
}
}
static async Task ReceiveMessagesForQueue(MessageQueue queue, bool isAsync)
{
while (true)
{
var message = await Task.Factory.FromAsync<Message>(queue.BeginReceive(), queue.EndReceive);
if (isAsync)
await ProcessMessageAsync(message);
else
ProcessMessage(message);
}
}
Async message processing
Uses await on Task.Delay(), so should release current Thread
static async Task ProcessMessageAsync(Message message)
{
await Task.Delay(1000);
BurnCpu();
}
Sync message processing
waits on Task.Delay(), so shouldn't release current Thread
static void ProcessMessage(Message message)
{
Task.Delay(1000).Wait();
BurnCpu();
}
In the end, results are equal. Am I missing something here?
Edit 1
I'm measuring overall time using stopWatch.ElapsedMilliseconds. I Fill all queues using FillAllQueuesWithMessages() with 10, 100, 10000 or more messages.
Edit 2
ReceiveMessagesForQueue() returns Task instead of void now.
Edit 3 (fix)
This test does show me performance improvement now. I had to make BurnCpu() take more time. While Task.Delay() is being awaited, BurnCPU() can use the released thread to process.
Using async-await doesn't speed up the time it takes to execute a single operation, it just means that you don't have a thread waiting doing nothing.
In your case Task.Delay will take a second no matter what but here:
Task.Delay(1000).Wait();
You have a thread that sits and waits for the second to end while here:
await Task.Delay(1000);
You don't. You are still asynchronously waiting (hence, await) but no thread is being used which means better scalability.
In async-await you get the performance boost because your app can do the same with less threads, or do more with the same threads. To measure that you need to have a lot of async operations concurrently. Only then will you notice that the async option utilizes CPU resources better than the synchronous one.
More info about freeing threads here There Is No Thread
You're still running each task in its own thread from the thread pool - as you're using the default task scheduler. If you want to see performance imporvement, you'll need to make sure several tasks are performed on the same thread.
Also, with 20 parallel tasks, you're probably not going to see any difference. Try it with 2,000 tasks.
I need a way to set an async task as long running without using Task.Factory.StartNew(...) and instead using Task.Run(...) or something similar.
Context:
I have Task that loops continuously until it is externally canceled that I would like to set as 'long running' (i.e. give it a dedicated thread). This can be achieved through the code below:
var cts = new CancellationTokenSource();
Task t = Task.Factory.StartNew(
async () => {
while (true)
{
cts.Token.ThrowIfCancellationRequested();
try
{
"Running...".Dump();
await Task.Delay(500, cts.Token);
}
catch (TaskCanceledException ex) { }
} }, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
The problem is that Task.Factory.StartNew(...) does not return the active async task that is passed in but rather a 'task of running the Action' which functionally always has taskStatus of 'RanToCompletion'. Since my code needs to be able to track the task's status to see when it becomes 'Canceled' (or 'Faulted') I need to use something like below:
var cts = new CancellationTokenSource();
Task t = Task.Run(
async () => {
while (true)
{
cts.Token.ThrowIfCancellationRequested();
try
{
"Running...".Dump();
await Task.Delay(500, cts.Token);
}
catch (TaskCanceledException ex) { }
} }, cts.Token);
Task.Run(...), as desired, returns the async process itself allowing me to obtain actual statuses of 'Canceled' or 'Faulted'. I cannot specify the task as long running, however. So, anyone know how to best go about running an async task while both storing that active task itself (with desired taskStatus) and setting the task to long running?
I have Task that loops continuously until it is externally canceled that I would like to set as 'long running' (i.e. give it a dedicated thread)... anyone know how to best go about running an async task while both storing that active task itself (with desired taskStatus) and setting the task to long running?
There's a few problems with this. First, "long running" does not necessarily mean a dedicated thread - it just means that you're giving the TPL a hint that the task is long-running. In the current (4.5) implementation, you will get a dedicated thread; but that's not guaranteed and could change in the future.
So, if you need a dedicated thread, you'll have to just create one.
The other problem is the notion of an "asynchronous task". What actually happens with async code running on the thread pool is that the thread is returned to the thread pool while the asynchronous operation (i.e., Task.Delay) is in progress. Then, when the async op completes, a thread is taken from the thread pool to resume the async method. In the general case, this is more efficient than reserving a thread specifically to complete that task.
So, with async tasks running on the thread pool, dedicated threads don't really make sense.
Regarding solutions:
If you do need a dedicated thread to run your async code, I'd recommend using the AsyncContextThread from my AsyncEx library:
using (var thread = new AsyncContextThread())
{
Task t = thread.TaskFactory.Run(async () =>
{
while (true)
{
cts.Token.ThrowIfCancellationRequested();
try
{
"Running...".Dump();
await Task.Delay(500, cts.Token);
}
catch (TaskCanceledException ex) { }
}
});
}
However, you almost certainly don't need a dedicated thread. If your code can execute on the thread pool, then it probably should; and a dedicated thread doesn't make sense for async methods running on the thread pool. More specifically, the long-running flag doesn't make sense for async methods running on the thread pool.
Put another way, with an async lambda, what the thread pool actually executes (and sees as tasks) are just the parts of the lambda in-between the await statements. Since those parts aren't long-running, the long-running flag is not required. And your solution becomes:
Task t = Task.Run(async () =>
{
while (true)
{
cts.Token.ThrowIfCancellationRequested(); // not long-running
try
{
"Running...".Dump(); // not long-running
await Task.Delay(500, cts.Token); // not executed by the thread pool
}
catch (TaskCanceledException ex) { }
}
});
Call Unwrap on the task returned from Task.Factory.StartNew this will return the inner task, which has the correct status.
var cts = new CancellationTokenSource();
Task t = Task.Factory.StartNew(
async () => {
while (true)
{
cts.Token.ThrowIfCancellationRequested();
try
{
"Running...".Dump();
await Task.Delay(500, cts.Token);
}
catch (TaskCanceledException ex) { }
} }, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap();
On a dedicated thread, there's nothing to yield to. Don't use async and await, use synchronous calls.
This question gives two ways to do a cancellable sleep without await:
Task.Delay(500, cts.Token).Wait(); // requires .NET 4.5
cts.WaitHandle.WaitOne(TimeSpan.FromMilliseconds(500)); // valid in .NET 4.0 and later
If part of your work does use parallelism, you can start parallel tasks, saving those into an array, and use Task.WaitAny on the Task[]. Still no use for await in the main thread procedure.
This is unnecessary and Task.Run will suffice as the Task Scheduler will set any task to LongRunning if it runs for more than 0.5 seconds.
See here why.
https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html
You need to specify custom TaskCreationOptions. Let’s consider each of
the options. AttachedToParent shouldn’t be used in async tasks, so
that’s out. DenyChildAttach should always be used with async tasks
(hint: if you didn’t already know that, then StartNew isn’t the tool
you need). DenyChildAttach is passed by Task.Run. HideScheduler might
be useful in some really obscure scheduling scenarios but in general
should be avoided for async tasks. That only leaves LongRunning and
PreferFairness, which are both optimization hints that should only be
specified after application profiling. I often see LongRunning misused
in particular. In the vast majority of situations, the threadpool will
adjust to any long-running task in 0.5 seconds - without the
LongRunning flag. Most likely, you don’t really need it.
The real issue you have here is that your operation is not in fact long running. The actual work you're doing is an asynchronous operation, meaning it will return to the caller basically immediately. So not only do you not need to use the long running hint when having the task scheduler schedule it, there's no need to even use a thread pool thread to do this work, because it'll be basically instantaneous. You shouldn't be using StartNew or Run at all, let alone with the long running flag.
So rather than taking your asynchronous method and starting it in another thread, you can just start it right on the current thread by calling the asynchronous method. Offloading the starting of an already asynchronous operation is just creating more work that'll make things slower.
So your code simplifies all the way down to:
var cts = new CancellationTokenSource();
Task t = DoWork();
async Task DoWork()
{
while (true)
{
cts.Token.ThrowIfCancellationRequested();
try
{
"Running...".Dump();
await Task.Delay(500, cts.Token);
}
catch (TaskCanceledException) { }
}
}
I think the consideration should be not how long the thread run but how much of its time is it really working.
In your example there is short work and them await Task.Delay(...).
If this is really the case in your project you probably shouldn't use a dedicated thread for this task and let it run on the regular thread pool. Every time you'll call await on an IO operation or on Task.Delay() you'll release the thread for other tasks to use.
You should only use LongRunning when you'll decrease your thread from the thread-pool and never give it back or give it back only for a small percentage of the time. In such a case (where the work is long and Task.Delay(...) is short in comparison) using a dedicated thread for the job is a reasonable solution.
On the other hand if your thread is really working most of the time it will consume system resources (CPU time) and maybe it doesn't really matter if it holds a thread of the thread-pool since it is preventing other work from happening anyway.
Conclusion? Just use Task.Run() (without LongRunning) and use await in your long running task when and if it is possible. Revert to LongRunning only when you actually see the other approach is causing you problems and even then check your code and design to make sure it is really necessary and there isn't something else you can change in your code.