Асинхронное выполнение скриптов

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

  1. CancellationToken: Всегда передавайте токен отмены для долгих операций
  2. Таймауты: Устанавливайте разумные таймауты для скриптов
  3. Параллелизм: Ограничивайте количество параллельных выполнений
  4. Изоляция: Используйте отдельные контексты для параллельных скриптов
  5. Обработка ошибок: Используйте Try* методы для критических операций
  6. Пулинг: Переиспользуйте контексты через пул
  7. Мониторинг: Отслеживайте длительность выполнения