之前不是想让Kokoro运行在C#中嘛
但项目赶得紧,所以就先通过PythonNet嫁接过来实现
Kokoro相应还是要一点时间的,挂主线程就会阻塞
作为CPU消耗型的任务,很自然的会想到Task.Run
但Task.Run基于的是线程池,每次运行分配的线程是不一样的
这会导致每次生成语音,Python上下文是不一致的
轻则需要重新加载模型文件,重则会导致难以估计的问题
所以需要一个后台线程来运行Python实例
这里先给出示例代码
public class PythonExecutor
{
private readonly Thread _pythonThread;
private readonly BlockingCollection<Func<object>> _tasks = new();
public PythonExecutor()
{
Runtime.PythonDLL = "Your Python Dll Path";
_pythonThread = new Thread(() =>
{
PythonEngine.Initialize();
PythonEngine.BeginAllowThreads();
foreach (var task in _taskQueue.GetConsumingEnumerable())
{
try
{
using (Py.GIL()) task();
}
catch (Exception ex)
{
Console.WriteLine($"Python task error: {ex}");
}
}
PythonEngine.Shutdown();
})
{
IsBackground = true
};
_pythonThread.Start();
}
public void Task<T> EnqueueAsync<T>(Func<T> pythonFunc)
{
var tcs = new TaskCompletionSource<T>();
_taskQueue.Add(() =>
{
try
{
var result = pythonFunc();
tcs.SetResult(result);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
return null!;
});
return tcs.Task;
}
// 对象析构时指定无后继任务,终止线程
~PythonExecutor()
{
_taskQueue.CompleteAdding();
}
}
调用示例
var py = new PythonExecutor();
Console.WriteLine("Do Heavy Calculate In Python");
int ans = await py.EnqueueAsync(()=>{
dynamic some_module = Py.Import("some_module");
PyObject result = some_module.heavy_cal();
return result.As<int>();
});
Console.WriteLine($"Ans: {ans}");
可通过await等待,不阻塞主线程,这样就很方便了