C
C#2y ago
Keyinator

Threading Return to old thread

I have the following code where the function on line 13 changes the thread to another id. How can I make it so that when Task.Run ends, I am back on the original Thread where CallOnMainThread started?
18 Replies
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
Keyinator
Keyinator2y ago
Basically the !server!-side code isn't multi-threading friendly and thus you often need to go to the main thread to execute some code. This helper is supposed to do the following: If already on main thread, execute the code If not: - go to main thread - execute the function - return to original (background) thread (in order to not put too much load on the main thread)
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
Keyinator
Keyinator2y ago
The problem is that I need a result back. It's not an Action but a Func<T>
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
Keyinator
Keyinator2y ago
I have to rephrase myself. It does not have to return to the original background thread but it should not stay on the main thread because then all following code will be run on the main thread subsequently putting a lot of load on it. My goal of this function / helper was to translate example code that would look like this:
[...]
DoWorkOnBackGround()
await Delay(0) //<-- This is the method that changes the thread to main thread
var x = DoWorkOnForeGroundAndRetrieveResult()
await Task.Run(async() => {
DoFurtherWorkOnBackGround(x)
}
[...]
DoWorkOnBackGround()
await Delay(0) //<-- This is the method that changes the thread to main thread
var x = DoWorkOnForeGroundAndRetrieveResult()
await Task.Run(async() => {
DoFurtherWorkOnBackGround(x)
}
[...]
DoWorkOnBackGround()
var x = await ThreadinHelper.RetrieveOnForeGround(() => {
return DoWorkOnForeGroundAndRetrieveResult();
}
DoFurtherWorkOnBackGround(x)
[...]
DoWorkOnBackGround()
var x = await ThreadinHelper.RetrieveOnForeGround(() => {
return DoWorkOnForeGroundAndRetrieveResult();
}
DoFurtherWorkOnBackGround(x)
Because in the first example when you need to go to the main thread more frequently you're going to nest more and more Task.Runs
Wz
Wz2y ago
GitHub
GitHub - microsoft/vs-threading: The Microsoft.VisualStudio.Threadi...
The Microsoft.VisualStudio.Threading is a xplat library that provides many threading and synchronization primitives used in Visual Studio and other applications. - GitHub - microsoft/vs-threading:...
Tvde1
Tvde12y ago
ideally you want one thread which responsibility is to communicate with the game. That thread should look at a queue of operations and execute them and post the result back you can create Task<T>s that resolve when the result is posted back, so the rest of your system is unaware that a specific thread does the work based on a queue it'll be
var result = await DoSomethingToTheGameAsync();
var result = await DoSomethingToTheGameAsync();
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
Tvde1
Tvde12y ago
I don't see why this pattern wouldn't work perfectly for their use case
Keyinator
Keyinator2y ago
It could work with that but there'd have to be some way of retrieving the evaluation after it's done.
Tvde1
Tvde12y ago
you could use a TaskCompletionSource it basicaly works like this:
async Task<string> DoWork()
{
var tcs = new TaskCompletionSource<string>();

Task.Delay(1000)
.ContinueWith(_ => tcs.SetResult("abc"));

return tcs.Task;
}

var result = await DoWork();
// result = "abc"
async Task<string> DoWork()
{
var tcs = new TaskCompletionSource<string>();

Task.Delay(1000)
.ContinueWith(_ => tcs.SetResult("abc"));

return tcs.Task;
}

var result = await DoWork();
// result = "abc"
note that DoWork immediately returns tcs.Task and that after a second, the Task.Delay causes the tcs.SetResult("abc") but DoWork returns a Task<string>. It's invisible when the task actually gets completed and how
Keyinator
Keyinator2y ago
Ahh. That sounds implementable So I would insert a touple with Func<T> and a TaskCompletionSource<T> In the worker I would evaluate Func and set the result for the source. (?)
Tvde1
Tvde12y ago
so you can do the following
class Operation
{
OperationData Data;
Task<string> Task => TaskCompletionSource.Task;
TaskCompletionSource TaskCompletionSource = new();
}

var operations = new Queue<Operation>();

// Thread 1
var operation = new Operation(data);
operations.Enqueue(operation);

var result = await operation.Task;

// Thread 2
while (true)
{
var op = operations.Dequeue();
DoStuff(op.Data, out string result);
op.TaskCompletionSource.SetResult(result);
}
class Operation
{
OperationData Data;
Task<string> Task => TaskCompletionSource.Task;
TaskCompletionSource TaskCompletionSource = new();
}

var operations = new Queue<Operation>();

// Thread 1
var operation = new Operation(data);
operations.Enqueue(operation);

var result = await operation.Task;

// Thread 2
while (true)
{
var op = operations.Dequeue();
DoStuff(op.Data, out string result);
op.TaskCompletionSource.SetResult(result);
}
Keyinator
Keyinator2y ago
Alright. I'll implement that and see where it gets me :)
Tvde1
Tvde12y ago
good luck!
Keyinator
Keyinator2y ago
Alright. I think I've got it done. However I also want to allow Actions (without parameters). Is there something else I could use for TaskCompletionSource or just use TaskCompletionSource<bool> and set to true?
Tvde1
Tvde12y ago
I don't think there is a TCS without generic parameter I'd use bool or object there yeah