wabbajack/Wabbajack.Common/WorkQueue.cs

125 lines
4.1 KiB
C#
Raw Normal View History

2019-07-22 22:17:46 +00:00
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
2019-07-22 22:17:46 +00:00
using System.Threading;
using System.Threading.Tasks;
2019-12-04 04:12:08 +00:00
using Wabbajack.Common.StatusFeed;
2019-07-22 22:17:46 +00:00
namespace Wabbajack.Common
{
public class WorkQueue : IDisposable
2019-07-22 22:17:46 +00:00
{
internal BlockingCollection<Func<Task>>
Queue = new BlockingCollection<Func<Task>>(new ConcurrentStack<Func<Task>>());
2019-07-22 22:17:46 +00:00
public const int UnassignedCpuId = 0;
private static readonly AsyncLocal<int> _cpuId = new AsyncLocal<int>();
public int CpuId => _cpuId.Value;
2019-07-22 22:17:46 +00:00
internal static bool WorkerThread => ThreadLocalCurrentQueue.Value != null;
internal static readonly ThreadLocal<WorkQueue> ThreadLocalCurrentQueue = new ThreadLocal<WorkQueue>();
internal static readonly AsyncLocal<WorkQueue> AsyncLocalCurrentQueue = new AsyncLocal<WorkQueue>();
2019-09-14 04:35:42 +00:00
2019-12-03 23:03:47 +00:00
private readonly Subject<CPUStatus> _Status = new Subject<CPUStatus>();
2019-11-17 04:16:42 +00:00
public IObservable<CPUStatus> Status => _Status;
2019-11-17 06:02:09 +00:00
2019-12-07 02:05:50 +00:00
public List<Thread> Threads { get; private set; }
2019-08-10 15:21:50 +00:00
2019-12-04 00:03:43 +00:00
private CancellationTokenSource _cancel = new CancellationTokenSource();
// This is currently a lie, as it wires to the Utils singleton stream This is still good to have,
// so that logic related to a single WorkQueue can subscribe to this dummy member so that If/when we
// implement log messages in a non-singleton fashion, they will already be wired up properly.
public IObservable<IStatusMessage> LogMessages => Utils.LogMessages;
public WorkQueue(int threadCount = 0)
2019-07-22 22:17:46 +00:00
{
StartThreads(threadCount == 0 ? Environment.ProcessorCount : threadCount);
2019-07-22 22:17:46 +00:00
}
private void StartThreads(int threadCount)
2019-07-22 22:17:46 +00:00
{
ThreadCount = threadCount;
Threads = Enumerable.Range(1, threadCount)
2019-09-14 04:35:42 +00:00
.Select(idx =>
{
var thread = new Thread(() => ThreadBody(idx).Wait());
2019-09-14 04:35:42 +00:00
thread.Priority = ThreadPriority.BelowNormal;
thread.IsBackground = true;
thread.Name = string.Format("Wabbajack_Worker_{0}", idx);
thread.Start();
return thread;
}).ToList();
2019-07-22 22:17:46 +00:00
}
public int ThreadCount { get; private set; }
private async Task ThreadBody(int idx)
2019-07-22 22:17:46 +00:00
{
_cpuId.Value = idx;
ThreadLocalCurrentQueue.Value = this;
AsyncLocalCurrentQueue.Value = this;
2019-07-22 22:17:46 +00:00
2019-12-04 00:03:43 +00:00
try
{
while (true)
{
Report("Waiting", 0, false);
if (_cancel.IsCancellationRequested) return;
Func<Task> f;
try
{
f = Queue.Take(_cancel.Token);
}
catch (Exception)
{
throw new OperationCanceledException();
}
await f();
2019-12-04 00:03:43 +00:00
}
}
catch (OperationCanceledException)
2019-07-22 22:17:46 +00:00
{
}
}
2019-09-14 04:35:42 +00:00
public void Report(string msg, int progress, bool isWorking = true)
2019-07-22 22:17:46 +00:00
{
2019-11-17 04:16:42 +00:00
_Status.OnNext(
new CPUStatus
{
Progress = progress,
ProgressPercent = progress / 100f,
2019-11-17 04:16:42 +00:00
Msg = msg,
ID = _cpuId.Value,
IsWorking = isWorking
2019-11-17 04:16:42 +00:00
});
2019-07-22 22:17:46 +00:00
}
public void QueueTask(Func<Task> a)
2019-07-22 22:17:46 +00:00
{
Queue.Add(a);
}
public void Dispose()
2019-08-02 23:04:04 +00:00
{
2019-12-04 00:03:43 +00:00
_cancel.Cancel();
Queue?.Dispose();
}
2019-07-22 22:17:46 +00:00
}
public class CPUStatus
{
public int Progress { get; internal set; }
public float ProgressPercent { get; internal set; }
public string Msg { get; internal set; }
public int ID { get; internal set; }
public bool IsWorking { get; internal set; }
}
}