在javascript中,我们可以调用SetTimeout进行异步操作,而不必担心线程安全问题,因为javascript是单线程的,而SetTimeout不会在新线程中执行代码块。
在C#中,我们可以使用Task类来使操作异步,如下所示。
Task.Factory.StartNew(()=> DoOperation());
但据我所知,DoOperation可以在主线程或新线程中发生,而Task不会让我们决定成为新线程并在同一个线程中。
新的异步功能不等同于SetTimeout。
如何在C#app中实现与Javascript完全相同的东西?有没有办法将Console App配置为单线程(我记得这样的事情)
修改 考虑下面我正在处理的用例场景。它是简化版本。
class Program
{
static BufferBlock<int> queue = new BufferBlock<int>();
static List<int> list = new List<int>();
static void Main(string[] args)
{
Task.WhenAll(Produce(), Consume()).ContinueWith(r=> Console.WriteLine("list count: "+list.Count));
Console.ReadKey();
}
public static async Task Produce()
{
var random = new Random();
for (int i = 0; i < 1000; i++)
{
var value = random.Next();
await queue.SendAsync(value);
await Task.Delay(random.Next(1, 4));
list.Add(value);//manipulate none thread safe object
Console.WriteLine("value produced " + value);
}
queue.Complete();
}
public static async Task Consume()
{
var random = new Random();
while (await queue.OutputAvailableAsync())
{
var value = await queue.ReceiveAsync();
//consume the value
await Task.Delay(random.Next(1, 4));
list.Remove(value);//manipulate none thread safe object
Console.WriteLine("value consumed " + value);
}
}
}
基本上是生产者/消费者模式和生产者添加值,消费者使用这些值。我们通过这个作业操作List对象,它不是线程安全的。
在任务列表结束时,计数应为0但不是因为2个任务访问无线程安全的List对象。我们肯定可以像锁定一样处理线程安全问题,但在我的用例中它是不必要的,低效的,太多锁定很难遵循并且很容易陷入死锁问题。
@Asad和其他人指出了正确的方向,所以感谢大家。答案 0 :(得分:2)
如果您只想确保运行的线程不超过一个(并且不一定所有内容都在同一个线程上运行),您可以使用独占调度程序来执行此操作:
static void Main(string[] args)
{
var sch = new ConcurrentExclusiveSchedulerPair().ExclusiveScheduler;
var tf = new TaskFactory(sch);
var t = tf.StartNew(() => Run()).Result;
t.Wait();
}
static async Task Run()
{
var start = DateTime.UtcNow;
var t = Task.Delay(1000).ContinueWith(_ =>
{
Console.WriteLine("I tried to log after 1 second, ended up logging after {0}", (DateTime.UtcNow - start).TotalSeconds);
});
Console.WriteLine("It has not yet been 1 second. I will hog the only thread available to demonstrate how I simulate JS behavior.");
Thread.Sleep(2000);
await t;
}
我正在使用ConcurrentExclusiveSchedulerPair.ExclusiveScheduler
创建一个新的任务工厂,我将用它来运行我的主程序入口点。
如果你运行这个程序,你会注意到即使日志被安排了1秒,它也被阻止了,因为一个线程已经没有做任何事情(这正是JS的行为)。删除Sleep
并按时调用超时回调。
最后,以下是使用单线程异步模拟JS setTimeout
行为的方法:
static async Task JSSetTimeout(int ms, Action callback, int waitResolution = 10)
{
var startTime = DateTime.UtcNow;
while ((DateTime.UtcNow - startTime).TotalMilliseconds < ms)
{
await Task.Delay(waitResolution);
}
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
callback();
}
答案 1 :(得分:1)
你想要的东西看起来真的很有趣,你可能错过了关于async / await的东西。话虽这么说,你也许可以使用
TaskFactory.StartNew Method (Func, CancellationToken, TaskCreationOptions, TaskScheduler)
并传递您自己的任务计划程序,例如
您希望使用此任务调度程序的所有代码都可能导致并发问题(因此不要在主线程上运行任何代码)。
答案 2 :(得分:0)
如果您正在寻找以单线程方式发生的Tasks
,请查看TaskScheduler
。
替代方法包括设置SynchronizationContext
- 如果您正在使用WPF应用程序,这是因为更容易访问。获得后,只需致电Post
即可将您的通话发送至&#34;下一个可用时间&#34; UI /主线程。
答案 3 :(得分:0)
听起来你的实际问题是如何以线程安全的方式实现生产者/消费者结构。您似乎尝试使用不是线程安全的集合来实现此功能,并尝试使用来自Javascript的技术添加线程安全性。
不需要setTimeout
来确保线程安全。在任何情况下,该调用都等同于一次windows timer,它总是在应用程序的UI线程上执行。 await Task.Delay()
还在内部使用了一次启动计时器,但确保在其原始上下文中恢复执行 - 在桌面应用程序上,即UI线程。
如果您可以让消费者独立工作,那么轮询是不必要的。即使您确实需要轮询,也可以使用其他计时器类和.NET对生产者/消费者的支持来实现它。
.NET已经提供了几个生产者/消费者实现,甚至提供了IProducerConsumerCollection抽象来组合它们。
IProducerConsumerCollection
的集合。默认情况下,它使用ConcurrentQueue集合来确保线程安全而不锁定。生产者只需添加元素,而消费者以阻塞方式读取项目。不需要轮询,因为消费者线程在等待时只是阻塞,并在发布新数据时立即响应。var myBlock=new ActionBlock(data=>DoSomething(data)); .... myBlock.Post(someOtherData);
List<T>
之类的不安全集合周围使用锁定,以防止多个生产者和消费者同时修改集合。