如何初始化和调用静态API总是从一个专用线程?



我有一个API,需要从初始化它的线程调用。我想做一个包装器,它将创建一个线程来运行对API的所有调用,然后允许我从UIawait这些调用。在c#中,不借助第三方库的优雅方式是什么?

我想象的结果看起来像这样:

static class SingleThreadedApi
{
public static void Init();
// Has to be called from the same thread as init.
public static double LongRunningCall();
}
class ApiWrapper
{
// ???
}
class MyWindow
{
ApiWrapper api = new();
async void OnLoad()
{
await api.InitAsync();
}
async void OnButtonClick()
{
var result = await api.LongRunningCallAsync();
answer.Text = "result: {result}";
}
}

这个问题类似于。net中在一个单独(单个)线程上管理任务队列的最佳方式,除了这些任务只需要串行运行,而不一定在同一个线程上。

如何创建自定义SynchronizationContext,以便所有的延续都可以由我自己的单线程事件循环处理?可能是一种解决办法,但我希望有比"不是世界上最容易的事情"更好的办法。我有一个开源[…]

)实现!">

到目前为止,我发现最好的解决方案是使用BlockingCollection<Action>TaskCompletionSource。简化后,如下所示:

static class SingleThreadedAPi
{
public static void Init();
// Has to be called from the same thread as init.
public static double LongRunningCall();
}
class ApiWrapper
{
BlockingCollection<Action> actionQueue = new();
public ApiWrapper()
{
new Thread(Run).Start();
}
public Task InitAsync()
{
var completion = new TaskCompletionSource();
actionQueue.Add(() =>
{
try
{
SingleThreadedAPi.Init();
completion.SetResult();
}
catch (Exception e)
{
completion.SetException(e);
}
});
return completion.Task;
}
public Task<double> LongRunningCallAsync()
{
var completion = new TaskCompletionSource<double>();
actionQueue.Add(() =>
{
try
{

completion.SetResult(SingleThreadedAPi.LongRunningCall());
}
catch (Exception e)
{
completion.SetException(e);
}
});
return completion.Task;
}
public void Finish()
{
actionQueue.CompleteAdding();
}
void Run()
{
foreach (var action in actionQueue.GetConsumingEnumerable())
action();
}
}
class MyWindow
{
ApiWrapper api;
async void OnLoad()
{
await api.InitAsync();
}
async void OnButtonClick()
{
var result = await api.LongRunningCallAsync();
answer.Text = "result: {result}";
}
}

几乎这个问题的任何解决方案都将以这样或那样的方式基于BlockingCollection<T>,问题变成了如何使与BlockingCollection<T>的交互更方便。我的建议是将它包装在一个自定义的TaskScheduler中,它使用一个专用线程,并在这个线程上执行所有任务。您可以在这篇旧文章中找到关于如何编写自定义TaskScheduler的材料(文章的源代码可以在这里找到),或者您可以直接使用我在这里发布的SingleThreadTaskScheduler实现。它可以这样使用:

class MyWindow
{
private SingleThreadTaskScheduler _apiTaskScheduler;
private TaskFactory _apiTaskFactory;
async void OnLoad()
{
_apiTaskScheduler = new();
_apiTaskFactory = new(_apiTaskScheduler);
await _apiTaskFactory.StartNew(() => SingleThreadedApi.Init());
}
async void OnButtonClick()
{
double result = await _apiTaskFactory
.StartNew(() => SingleThreadedApi.LongRunningCall());
answer.Text = "result: {result}";
}
void OnClosed()
{
_apiTaskScheduler.Dispose();
}
}

Dispose为阻塞呼叫。它将阻塞UI线程,直到所有计划执行的任务完成,并且专用线程被终止。这并不理想,但是允许在所有操作完成之前关闭窗口可能更糟糕。

最新更新