自定义 TaskFactory 不使用自定义 SynchronizationContext
Custom TaskFactory does not use custom SynchronizationContext
注:这是在Unity3D下
我需要 运行 使用自定义同步上下文的内部任务,如下所示:
- 外部任务 -> 在默认调度程序上
- 内部任务 -> 在具有自定义同步上下文的自定义调度程序上
这是因为内部任务中的某些对象只能在自定义同步上下文将 post 到的特定线程上创建,另一方面,我希望外部任务是 运行 通常,即 ThreadPool
或 Task.Run
使用的任何内容。
遵循这个问题的建议:
How to run a Task on a custom TaskScheduler using await?
不幸的是,这不起作用,同步上下文仍然是默认的:
OUTER JOB: 'null'
0
INNER JOB: 'null' <------------- this context should not be the default one
0, 1, 2, 3, 4,
1
INNER JOB: 'null'
0, 1, 2, 3, 4,
2
INNER JOB: 'null'
0, 1, 2, 3, 4,
Done, press any key to exit
代码:
using System;
using System.Threading;
using System.Threading.Tasks;
internal static class Program
{
private static TaskFactory CustomFactory { get; set; }
private static async Task Main(string[] args)
{
// create a custom task factory with a custom synchronization context,
// and make sure to restore initial context after it's been created
var initial = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(new CustomSynchronizationContext());
CustomFactory = new TaskFactory(
CancellationToken.None,
TaskCreationOptions.DenyChildAttach,
TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext()
);
SynchronizationContext.SetSynchronizationContext(initial);
// now do some work on initial context
await Task.Run(async () =>
{
Console.WriteLine($"OUTER JOB: {GetCurrentContext()}");
for (var i = 0; i < 3; i++)
{
Console.WriteLine(i);
await Task.Delay(100);
// now do some work on custom context
await RunOnCustomScheduler(DoSpecialWork);
}
});
Console.WriteLine("Done, press any key to exit");
Console.ReadKey();
}
private static void DoSpecialWork()
{
Console.WriteLine($"\tINNER JOB: {GetCurrentContext()}"); // TODO wrong context
for (var i = 0; i < 5; i++)
{
Thread.Sleep(250);
Console.Write($"\t{i}, ");
}
Console.WriteLine();
}
private static Task RunOnCustomScheduler(Action action)
{
return CustomFactory.StartNew(action);
}
private static string GetCurrentContext()
{
return SynchronizationContext.Current?.ToString() ?? "'null'";
}
}
public class CustomSynchronizationContext : SynchronizationContext
{
}
当我调试时,我可以看到自定义工厂确实有带有自定义上下文的自定义调度程序,但实际上它不起作用。
问题:
如何让我的自定义调度程序使用创建它的自定义上下文?
最终答案:
我正在等待我想避免使用的线程内部方法,将这些东西包装在 await Task.Run
中(加上一些为简洁起见而省略的细节)修复了它。现在我可以 运行 我在 Unity 线程之外的长期任务,但仍然使用上面提到的自定义工厂方法在其中进行 Unity 调用。
即它已经在工作了,但我没有正确使用它
您可以使用 ReactiveExtensions (nuget System.Reactive) 轻松实现:
public static Task RunOnCustomScheduler(Action action, IScheduler scheduler)
{
return Observable.Return(Unit.Default)
.ObserveOn(scheduler) // switch context
.Do(_ => action.Invoke()) // do the work
// .Select(_ => func.Invoke()) // change Action to Func if you want to return stuff.
//.Select(async _ => await func.Invoke()) // can work with async stuff, though ConfigureAwait may become tricky
.ToTask();
}
您也可以传递 SynchronizationContext
而不是 IScheduler。
记得 ConfigureAwait(false/true)
虽然当您调用此方法以配置您希望如何继续时
您的 CustomSynchronizationContext
确实用于 运行 宁 DoSpecialWork
方法。您可以通过覆盖基础 class:
的 Post
方法来确认这一点
public class CustomSynchronizationContext : SynchronizationContext
{
public override void Post(SendOrPostCallback d, object state)
{
Console.WriteLine($"@Post");
base.Post(d, state);
}
}
然后再次 运行 你的程序,你会看到三个 @Post 出现在控制台中。
SynchronizationContext.Current
is null
is because this property is associated with the current thread. Here is the source code的原因:
// Get the current SynchronizationContext on the current thread
public static SynchronizationContext Current
{
get
{
return Thread.CurrentThread.GetExecutionContextReader().SynchronizationContext ?? GetThreadLocalContext();
}
}
您可以在 ThreadPool
的每个线程中安装 CustomSynchronizationContext
,但不推荐这样做。这也不是必需的。在没有 SynchronizationContext.Current
的情况下,异步延续在当前 TaskScheduler
中是 运行ning,而 DoSpecialWorkAsync
方法中的当前 TaskScheduler
是您已使用与您的 CustomSynchronizationContext
实例关联的 TaskScheduler.FromCurrentSynchronizationContext()
方法创建了自己。
注:这是在Unity3D下
我需要 运行 使用自定义同步上下文的内部任务,如下所示:
- 外部任务 -> 在默认调度程序上
- 内部任务 -> 在具有自定义同步上下文的自定义调度程序上
这是因为内部任务中的某些对象只能在自定义同步上下文将 post 到的特定线程上创建,另一方面,我希望外部任务是 运行 通常,即 ThreadPool
或 Task.Run
使用的任何内容。
遵循这个问题的建议:
How to run a Task on a custom TaskScheduler using await?
不幸的是,这不起作用,同步上下文仍然是默认的:
OUTER JOB: 'null'
0
INNER JOB: 'null' <------------- this context should not be the default one
0, 1, 2, 3, 4,
1
INNER JOB: 'null'
0, 1, 2, 3, 4,
2
INNER JOB: 'null'
0, 1, 2, 3, 4,
Done, press any key to exit
代码:
using System;
using System.Threading;
using System.Threading.Tasks;
internal static class Program
{
private static TaskFactory CustomFactory { get; set; }
private static async Task Main(string[] args)
{
// create a custom task factory with a custom synchronization context,
// and make sure to restore initial context after it's been created
var initial = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(new CustomSynchronizationContext());
CustomFactory = new TaskFactory(
CancellationToken.None,
TaskCreationOptions.DenyChildAttach,
TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext()
);
SynchronizationContext.SetSynchronizationContext(initial);
// now do some work on initial context
await Task.Run(async () =>
{
Console.WriteLine($"OUTER JOB: {GetCurrentContext()}");
for (var i = 0; i < 3; i++)
{
Console.WriteLine(i);
await Task.Delay(100);
// now do some work on custom context
await RunOnCustomScheduler(DoSpecialWork);
}
});
Console.WriteLine("Done, press any key to exit");
Console.ReadKey();
}
private static void DoSpecialWork()
{
Console.WriteLine($"\tINNER JOB: {GetCurrentContext()}"); // TODO wrong context
for (var i = 0; i < 5; i++)
{
Thread.Sleep(250);
Console.Write($"\t{i}, ");
}
Console.WriteLine();
}
private static Task RunOnCustomScheduler(Action action)
{
return CustomFactory.StartNew(action);
}
private static string GetCurrentContext()
{
return SynchronizationContext.Current?.ToString() ?? "'null'";
}
}
public class CustomSynchronizationContext : SynchronizationContext
{
}
当我调试时,我可以看到自定义工厂确实有带有自定义上下文的自定义调度程序,但实际上它不起作用。
问题:
如何让我的自定义调度程序使用创建它的自定义上下文?
最终答案:
我正在等待我想避免使用的线程内部方法,将这些东西包装在 await Task.Run
中(加上一些为简洁起见而省略的细节)修复了它。现在我可以 运行 我在 Unity 线程之外的长期任务,但仍然使用上面提到的自定义工厂方法在其中进行 Unity 调用。
即它已经在工作了,但我没有正确使用它
您可以使用 ReactiveExtensions (nuget System.Reactive) 轻松实现:
public static Task RunOnCustomScheduler(Action action, IScheduler scheduler)
{
return Observable.Return(Unit.Default)
.ObserveOn(scheduler) // switch context
.Do(_ => action.Invoke()) // do the work
// .Select(_ => func.Invoke()) // change Action to Func if you want to return stuff.
//.Select(async _ => await func.Invoke()) // can work with async stuff, though ConfigureAwait may become tricky
.ToTask();
}
您也可以传递 SynchronizationContext
而不是 IScheduler。
记得 ConfigureAwait(false/true)
虽然当您调用此方法以配置您希望如何继续时
您的 CustomSynchronizationContext
确实用于 运行 宁 DoSpecialWork
方法。您可以通过覆盖基础 class:
Post
方法来确认这一点
public class CustomSynchronizationContext : SynchronizationContext
{
public override void Post(SendOrPostCallback d, object state)
{
Console.WriteLine($"@Post");
base.Post(d, state);
}
}
然后再次 运行 你的程序,你会看到三个 @Post 出现在控制台中。
SynchronizationContext.Current
is null
is because this property is associated with the current thread. Here is the source code的原因:
// Get the current SynchronizationContext on the current thread
public static SynchronizationContext Current
{
get
{
return Thread.CurrentThread.GetExecutionContextReader().SynchronizationContext ?? GetThreadLocalContext();
}
}
您可以在 ThreadPool
的每个线程中安装 CustomSynchronizationContext
,但不推荐这样做。这也不是必需的。在没有 SynchronizationContext.Current
的情况下,异步延续在当前 TaskScheduler
中是 运行ning,而 DoSpecialWorkAsync
方法中的当前 TaskScheduler
是您已使用与您的 CustomSynchronizationContext
实例关联的 TaskScheduler.FromCurrentSynchronizationContext()
方法创建了自己。