Асинхронное выполнение скриптов
IgdrasilScripting полностью поддерживает асинхронное выполнение скриптов с async/await и CancellationToken.
Основы
Асинхронная компиляция
// Скомпилировать асинхронно
var script = await engine.CompileAsync("return 2 + 2");
// С отменой
var cts = new CancellationTokenSource();
var script = await engine.CompileAsync("return 2 + 2", cts.Token);
Асинхронное выполнение
// Выполнить без результата
await engine.ExecuteAsync("print('Hello')");
// Вычислить с результатом
var result = await engine.EvaluateAsync("return 42");
// С отменой
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var result = await engine.EvaluateAsync("return longComputation()", cts.Token);
Безопасное асинхронное выполнение
// TryEvaluateAsync возвращает кортеж (success, result)
var (success, result) = await engine.TryEvaluateAsync("return 1 / x");
if (success)
{
Console.WriteLine($"Результат: {result}");
}
else
{
Console.WriteLine("Выполнение завершилось с ошибкой");
}
Отмена выполнения
Таймаут
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
try
{
var result = await engine.EvaluateAsync(@"
local result = 0
for i = 1, 1000000000 do
result = result + 1
end
return result
", cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Скрипт превысил таймаут в 10 секунд");
}
Ручная отмена
var cts = new CancellationTokenSource();
// Запуск скрипта
var scriptTask = engine.ExecuteAsync("longRunningScript()", cts.Token);
// Где-то в другом потоке
if (userPressedCancel)
{
cts.Cancel();
}
try
{
await scriptTask;
}
catch (OperationCanceledException)
{
Console.WriteLine("Скрипт отменен пользователем");
}
Параллельное выполнение
Несколько скриптов
var scripts = new[]
{
"return math.sqrt(144)",
"return 2 ^ 10",
"return string.upper('hello')"
};
// Выполнить все параллельно
var tasks = scripts.Select(s => engine.EvaluateAsync(s));
var results = await Task.WhenAll(tasks);
foreach (var result in results)
{
Console.WriteLine(result[0]);
}
С ограничением параллелизма
public class ScriptExecutor
{
private readonly SemaphoreSlim _semaphore;
private readonly IScriptEngine<object[], LuaModuleInterface> _engine;
public ScriptExecutor(IScriptEngine<object[], LuaModuleInterface> engine, int maxParallel = 4)
{
_engine = engine;
_semaphore = new SemaphoreSlim(maxParallel);
}
public async Task<object[]> ExecuteAsync(string script, CancellationToken cancellationToken = default)
{
await _semaphore.WaitAsync(cancellationToken);
try
{
return await _engine.EvaluateAsync(script, cancellationToken);
}
finally
{
_semaphore.Release();
}
}
}
// Использование
var executor = new ScriptExecutor(engine, maxParallel: 4);
var tasks = Enumerable.Range(0, 100)
.Select(i => executor.ExecuteAsync($"return {i} * 2"));
var results = await Task.WhenAll(tasks);
Прогресс выполнения
public class ProgressTracker
{
private int _completed;
private readonly int _total;
public ProgressTracker(int total)
{
_total = total;
}
public void Increment()
{
Interlocked.Increment(ref _completed);
var progress = _completed / (double)_total * 100;
Console.WriteLine($"Прогресс: {progress:F1}%");
}
}
// Использование
var scripts = GetScripts(); // 100 скриптов
var tracker = new ProgressTracker(scripts.Length);
var tasks = scripts.Select(async script =>
{
var result = await engine.EvaluateAsync(script);
tracker.Increment();
return result;
});
await Task.WhenAll(tasks);
Изолированные контексты
// Создать контекст
var context = engine.CreateContext();
// Установить переменные в контексте
context.SetValue("userId", 123);
context.SetValue("userName", "Player");
// Выполнить скрипт в контексте
// (требуется реализация в движке)
await engine.ExecuteAsync("print(userName)", context: context);
// Получить результат из контекста
var score = context.GetValue<int>("score");
// Удалить контекст
engine.RemoveContext(context.ContextId);
Пул контекстов
public class ScriptContextPool
{
private readonly ConcurrentBag<IScriptContext> _pool = new();
private readonly IScriptEngine<object[], LuaModuleInterface> _engine;
private readonly int _maxPoolSize;
public ScriptContextPool(IScriptEngine<object[], LuaModuleInterface> engine, int maxPoolSize = 10)
{
_engine = engine;
_maxPoolSize = maxPoolSize;
}
public IScriptContext Rent()
{
if (_pool.TryTake(out var context))
{
return context;
}
return _engine.CreateContext();
}
public void Return(IScriptContext context)
{
if (_pool.Count < _maxPoolSize)
{
context.Clear();
_pool.Add(context);
}
}
}
// Использование
var pool = new ScriptContextPool(engine);
var tasks = Enumerable.Range(0, 100).Select(async i =>
{
var context = pool.Rent();
try
{
context.SetValue("iteration", i);
return await engine.EvaluateAsync("return iteration * 2");
}
finally
{
pool.Return(context);
}
});
await Task.WhenAll(tasks);
Пакетное выполнение
public class BatchScriptExecutor
{
private readonly IScriptEngine<object[], LuaModuleInterface> _engine;
public BatchScriptExecutor(IScriptEngine<object[], LuaModuleInterface> engine)
{
_engine = engine;
}
public async Task<Dictionary<string, object[]>> ExecuteBatchAsync(
Dictionary<string, string> scripts,
CancellationToken cancellationToken = default)
{
var results = new ConcurrentDictionary<string, object[]>();
var tasks = scripts.Select(async kvp =>
{
try
{
var result = await _engine.EvaluateAsync(kvp.Value, cancellationToken);
results[kvp.Key] = result;
}
catch (Exception ex)
{
Console.WriteLine($"Ошибка в скрипте '{kvp.Key}': {ex.Message}");
}
});
await Task.WhenAll(tasks);
return new Dictionary<string, object[]>(results);
}
}
// Использование
var executor = new BatchScriptExecutor(engine);
var scripts = new Dictionary<string, string>
{
["calculation1"] = "return 2 + 2",
["calculation2"] = "return math.sqrt(16)",
["calculation3"] = "return string.upper('hello')"
};
var results = await executor.ExecuteBatchAsync(scripts);
foreach (var (name, result) in results)
{
Console.WriteLine($"{name}: {result[0]}");
}
Повторные попытки
public class RetryableScriptExecutor
{
private readonly IScriptEngine<object[], LuaModuleInterface> _engine;
private readonly int _maxRetries;
private readonly TimeSpan _retryDelay;
public RetryableScriptExecutor(
IScriptEngine<object[], LuaModuleInterface> engine,
int maxRetries = 3,
TimeSpan? retryDelay = null)
{
_engine = engine;
_maxRetries = maxRetries;
_retryDelay = retryDelay ?? TimeSpan.FromMilliseconds(100);
}
public async Task<object[]> ExecuteWithRetryAsync(
string script,
CancellationToken cancellationToken = default)
{
Exception? lastException = null;
for (int i = 0; i < _maxRetries; i++)
{
try
{
return await _engine.EvaluateAsync(script, cancellationToken);
}
catch (Exception ex)
{
lastException = ex;
if (i < _maxRetries - 1)
{
Console.WriteLine($"Попытка {i + 1} не удалась, повтор через {_retryDelay}");
await Task.Delay(_retryDelay, cancellationToken);
}
}
}
throw new Exception($"Не удалось выполнить скрипт после {_maxRetries} попыток", lastException);
}
}
// Использование
var executor = new RetryableScriptExecutor(engine, maxRetries: 3);
var result = await executor.ExecuteWithRetryAsync("return unstableFunction()");
Кеширование с async
public class AsyncCachedEngine
{
private readonly IScriptEngine<object[], LuaModuleInterface> _engine;
private readonly SemaphoreSlim _compileLock = new(1, 1);
public async Task<IScript<object[]>> CompileAsync(string script, CancellationToken cancellationToken = default)
{
var hash = ComputeHash(script);
// Попытка получить из кеша
var cached = _engine.Cache?.TryGetCached(hash);
if (cached != null)
{
return cached;
}
// Блокировка для предотвращения дублирования компиляции
await _compileLock.WaitAsync(cancellationToken);
try
{
// Повторная проверка кеша после получения блокировки
cached = _engine.Cache?.TryGetCached(hash);
if (cached != null)
{
return cached;
}
// Компиляция
var compiled = await _engine.CompileAsync(script, cancellationToken);
_engine.Cache?.Cache(hash, compiled);
return compiled;
}
finally
{
_compileLock.Release();
}
}
private static string ComputeHash(string script)
{
using var sha256 = SHA256.Create();
var bytes = Encoding.UTF8.GetBytes(script);
var hash = sha256.ComputeHash(bytes);
return Convert.ToBase64String(hash);
}
}
Best Practices
- CancellationToken: Всегда передавайте токен отмены для долгих операций
- Таймауты: Устанавливайте разумные таймауты для скриптов
- Параллелизм: Ограничивайте количество параллельных выполнений
- Изоляция: Используйте отдельные контексты для параллельных скриптов
- Обработка ошибок: Используйте Try* методы для критических операций
- Пулинг: Переиспользуйте контексты через пул
- Мониторинг: Отслеживайте длительность выполнения