B
You can, with one exception--- Unity forbids to cause most of its API outside its main flow♪Class Debug and its methods may, by the way, be summoned in subsidiaries.What's in "rantime" that in the editor, Unity has its implementation SynchronizationContext-- https://github.com/Unity-Technologies/UnityCsReference/blob/master/Runtime/Export/Scripting/UnitySynchronizationContext.cs Besides, we don't need anything in principle.The trivial task is carried out simply to the indiscretion:CancellationTokenSource _cts;
var token = _cts.Token;
var context = SynchronizationContext.Current;
Task.Run(() => DoWork(context, ...), token);
That's all it was, the question is to get back to the main flow after that, and then to some window.Well, we don't have a lot of options, but more exactly one-- EditorWindow.GetWindow<T>which either creates a window or restores a retained window or, at best, focuses on the open window.For the Bacgraund, the slip would like the window to be updated, but the user didn't dergalo and took the trick from the active windows. We have two choices:Singleton is a classic, but it's not really interesting to ask for a solution:GetWindow<T>("Title", focus: false) - I'll have to re-route the headline, but what to do.We'll stop on the second option and get something like that:private static CustomWindow GetCustomWindow(bool focus) {
return GetWindow<CustomWindow>("Task example", focus);
}
It's all trivial.There's some kind of "progress" in your window:private float _progress = 0f;
private float Progress {
set {
_progress = value;
// этот вызов перерисовки обязателен - без него не обновится визуал
Repaint();
}
}
We do important things in a wheelchair.private static void DumbTaskExample(int subTaskCount, int sleepTime, SynchronizationContext context, CancellationToken token) {
Debug.Log($"Task started at {Thread.CurrentThread.ManagedThreadId}");
for (int i = 0; i < subTaskCount; i++) {
// Cancellation
token.ThrowIfCancellationRequested();
// Work
Thread.Sleep(sleepTime);
// Notification
context.Post(_ => GetCustomWindow(false).Progress = (float)i/subTaskCount, null);
}
Debug.Log($"Task done at {Thread.CurrentThread.ManagedThreadId}");
context.Post(_ => GetCustomWindow(true).OnTaskFinishedOrCanceled(), null);
}
Debug.Log() It's just for the sake of logs, it's easier to settle. The context makes reference to the window and gives it the necessary methods.The mysterious method at the end of the mask is the release of resources and flags:private void OnTaskFinishedOrCanceled() {
_taskRunning = false;
_cts.Dispose();
_cts = null;
}
There's no such thing as calling or revoking them:private void OnGUI() {
ProgressBar();
if (_taskRunning) {
if (GUILayout.Button("Cancel task")) {
_cts.Cancel();
OnTaskFinishedOrCanceled();
}
}
else {
_sleepTaskCount = EditorGUILayout.IntField("Sleeping tasks count:", _sleepTaskCount);
_sleepTaskTime = EditorGUILayout.IntField("Sleeping time (ms):", _sleepTaskTime);
if (GUILayout.Button("Start single task") && _sleepTaskCount > 0) {
_progress = 0f;
_taskRunning = true;
_cts = new CancellationTokenSource();
var token = _cts.Token;
var context = SynchronizationContext.Current;
Task.Run(() => DumbTaskExample(_sleepTaskCount, _sleepTaskTime, context, token), token);
}
}
}
private void ProgressBar() {
var size = position.size;
var fullRect = GUILayoutUtility.GetRect(size.x, 30);
var completedRect = new Rect(fullRect.x, fullRect.y, fullRect.width * _progress, fullRect.height);
EditorGUI.DrawRect(fullRect, Color.black);
EditorGUI.DrawRect(completedRect, Color.Lerp(Color.red, Color.green, _progress));
EditorGUI.LabelField(fullRect, $"{_progress * 100}%", EditorStyles.centeredGreyMiniLabel);
}
The result is: You don't have to forget about the chain. For example, if several consecutive tasks were to be carried out, the outcome of each task depended on the previous one and the results should be verified at all stages. Well, or some stage could have left an exception.Writing an exception hendler:private static void HandleTaskException(Task task) {
Exception ex = task.Exception;
while (ex is AggregateException && ex.InnerException != null)
ex = ex.InnerException;
EditorUtility.DisplayDialog("Task chain terminated", $"Exception: {ex.Message}", "Ok");
}
Create a chain:Task.Run(() => DumbTaskExample(_sleepTaskCount, _sleepTaskTime, context, token), token)
.ContinueWith(
t => { HandleTaskException(t); OnTaskFinishedOrCanceled(); },
token,
TaskContinuationOptions.OnlyOnFaulted,
TaskScheduler.FromCurrentSynchronizationContext()
);
It is very important to point out the planner of the current context, or the continuation will be carried out in the same place as the disk outside the main flow of Unity.It's not very beautiful, but it's blocking the entire UI Unity dialogue window: Full codecode placed in the container for the spoilerusing System;
using System.Threading;
using System.Threading.Tasks;
using UnityEditor;
using UnityEngine;
public class CustomWindow : EditorWindow {
[MenuItem("Window/Task example")]
private static void ShowWindow() {
GetCustomWindow(true).Show();
}
private static CustomWindow GetCustomWindow(bool focus) {
return GetWindow<CustomWindow>("Task example", focus);
}
private float _progress = 0f;
private float Progress {
set {
_progress = value;
Repaint();
}
}
private bool _taskRunning;
private int _sleepTaskCount = 3;
private int _sleepTaskTime = 1000;
private CancellationTokenSource _cts;
private void OnGUI() {
ProgressBar();
if (_taskRunning) {
if (GUILayout.Button("Cancel task")) {
_cts.Cancel();
OnTaskFinishedOrCanceled();
}
}
else {
_sleepTaskCount = EditorGUILayout.IntField("Sleeping sub-tasks count:", _sleepTaskCount);
_sleepTaskTime = EditorGUILayout.IntField("sleeping time (ms):", _sleepTaskTime);
if (GUILayout.Button("Start single task") && _sleepTaskCount > 0) {
_progress = 0f;
_taskRunning = true;
_cts = new CancellationTokenSource();
var token = _cts.Token;
var context = SynchronizationContext.Current;
Task.Run(() => DumbTaskExample(_sleepTaskCount, _sleepTaskTime, context, token), token);
}
else if (GUILayout.Button("Start chained task") && _sleepTaskCount > 0) {
_progress = 0f;
_taskRunning = true;
_cts = new CancellationTokenSource();
var token = _cts.Token;
var context = SynchronizationContext.Current;
Task.Run(() => DumbTaskExample(_sleepTaskCount, _sleepTaskTime, context, token), token)
.ContinueWith(
t => { HandleTaskException(t); OnTaskFinishedOrCanceled(); },
token,
TaskContinuationOptions.OnlyOnFaulted,
TaskScheduler.FromCurrentSynchronizationContext()
);
}
}
}
private void ProgressBar() {
var size = position.size;
var fullRect = GUILayoutUtility.GetRect(size.x, 30);
var completedRect = new Rect(fullRect.x, fullRect.y, fullRect.width * _progress, fullRect.height);
EditorGUI.DrawRect(fullRect, Color.black);
EditorGUI.DrawRect(completedRect, Color.Lerp(Color.red, Color.green, _progress));
EditorGUI.LabelField(fullRect, $"{_progress * 100}%", EditorStyles.centeredGreyMiniLabel);
}
private void OnTaskFinishedOrCanceled() {
_taskRunning = false;
_cts.Dispose();
_cts = null;
}
private static void DumbTaskExample(int subTaskCount, int sleepTime, SynchronizationContext context, CancellationToken token) {
Debug.Log($"Task started at {Thread.CurrentThread.ManagedThreadId}");
for (int i = 0; i < subTaskCount; i++) {
// Cancellation
token.ThrowIfCancellationRequested();
// Work
Thread.Sleep(sleepTime);
// Notification
context.Post(_ => GetCustomWindow(false).Progress = (float)i/subTaskCount, null);
}
Debug.Log($"Task done at {Thread.CurrentThread.ManagedThreadId}");
context.Post(_ => GetCustomWindow(true).OnTaskFinishedOrCanceled(), null);
}
private static void HandleTaskException(Task task) {
if (task.IsFaulted) {
Exception ex = task.Exception;
while (ex is AggregateException && ex.InnerException != null)
ex = ex.InnerException;
EditorUtility.DisplayDialog("Task chain terminated", $"Exception: {ex.Message}", "Ok");
}
}
}TotalWe've got a pretty simple way of writing a way to create slips and even a chain of dispensaries. It is still possible to draw the thickness of the lifting, the handling of these situations, the handling of window closures, etc.The answer, without any thinness, has been cumbersome, and on that basis, you can already make any strings on top of it - it would be a wish :