0. はじめに
Freeradicalの中の人、yamarahです。
以前の記事(Autodesk Inventor API Hacking (NET環境での別スレッドUI))の続きというか、その後の経過です。
1. SynchronizationContext越えの呼び出し
不幸にも複数のUIスレッドを持つ場合に、
// 待たない。投げっぱなし。
context.Post(_ => action(), null);
// 完了するまでロックする。
context.Send(_ => action(), null);
だと、不便です。具体的にはawait
したい。
もっと言えば、呼び出し先の例外も受けたい。
ということで、標準化しました。
using System;
using System.Threading;
using System.Threading.Tasks;
public static class CallOverSynhronizationContextHelper
{
public static async Task InvokeActionAsync(Action action, SynchronizationContext context)
{
Exception? exception = null;
var sem = new SemaphoreSlim(0, 1);
context.Post(_ =>
{
try
{
action();
}
catch (Exception ex)
{
exception = ex;
}
finally
{
_ = sem.Release();
}
}, null);
await sem.WaitAsync();
if (exception is not null) throw exception;
}
public static async Task InvokeAsyncActionAsync(Func<Task> actionAsync, SynchronizationContext context)
{
Exception? exception = null;
var sem = new SemaphoreSlim(0, 1);
context.Post(async _ =>
{
try
{
await actionAsync();
}
catch (Exception ex)
{
exception = ex;
}
finally
{
_ = sem.Release();
}
}, null);
await sem.WaitAsync();
if (exception is not null) throw exception;
}
public static async Task<T> InvokeFunctionAsync<T>(Func<T> func, SynchronizationContext context)
{
Exception? exception = null;
var sem = new SemaphoreSlim(0, 1);
T? result = default;
context.Post(_ =>
{
try
{
result = func();
}
catch (Exception ex)
{
exception = ex;
}
finally
{
_ = sem.Release();
}
}, null);
await sem.WaitAsync();
return exception is null ? result! : throw exception;
}
public static async Task<T> InvokeAsyncFunctionAsync<T>(Func<Task<T>> funcAsync, SynchronizationContext context)
{
Exception? exception = null;
var sem = new SemaphoreSlim(0, 1);
T? result = default;
context.Post(async _ =>
{
try
{
result = await funcAsync();
}
catch (Exception ex)
{
exception = ex;
}
finally
{
_ = sem.Release();
}
}, null);
await sem.WaitAsync();
return exception is null ? result! : throw exception;
}
}
戻り値も取れるし、とても幸せ。