Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/327.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何确保跨线程的数据同步;“安全”;区域(例如,不在关键区域)未锁定所有内容_C#_Multithreading_Concurrency_Synchronization_Task - Fatal编程技术网

C# 如何确保跨线程的数据同步;“安全”;区域(例如,不在关键区域)未锁定所有内容

C# 如何确保跨线程的数据同步;“安全”;区域(例如,不在关键区域)未锁定所有内容,c#,multithreading,concurrency,synchronization,task,C#,Multithreading,Concurrency,Synchronization,Task,我们正在使用一个专有的API,它需要在某个点上同步数据。 我曾考虑过一些确保数据一致性的方法,但我渴望获得更多关于更好解决方案的信息 下面是一个长时间运行的任务概述了API同步 new Task(() => { while(true) { // Here other threads can access any API object (that's fine) API.CriticalOperationStart(); // Between

我们正在使用一个专有的API,它需要在某个点上同步数据。 我曾考虑过一些确保数据一致性的方法,但我渴望获得更多关于更好解决方案的信息

下面是一个长时间运行的
任务
概述了API同步

new Task(() =>
{
    while(true)
    {
        // Here other threads can access any API object (that's fine)

        API.CriticalOperationStart(); // Between start and end no API Object may be used
        API.CriticalOperationEnd();

        // Here other threads can access any API object (that's fine too)
    }
}, TaskCreationOptions.LongRunning).Start();
这是一个单独的任务,实际执行一些数据同步

起点和终点之间的区域至关重要当API处于此关键步骤时,可以执行其他API调用

下面是一些使用不同API对象的非保护线程:

// multiple calls to different API objects should not be exclusive
OtherThread1
APIObject1.SetData(42);

OtherThread2
APIObject2.SetData(43);
约束条件

在API处于关键部分期间,不允许调用任何
APIObject
方法。 两个
SetData
调用都允许同时执行。它们彼此不干涉,只干涉临界截面。 一般来说,从多个线程访问一个
APIObject
不是线程安全的,但是访问多个
APIObjects
不会干扰API,除非在关键部分。 在使用任何
APIObject
方法时,决不能执行临界部分。 不需要从多个线程保护对一个
APIObject
的访问


琐碎的方法

使用一个lock对象和
lock
关键部分以及对API对象的每次调用

这将有效地工作,但会创建许多不必要的锁,因为一次只能访问一个
APIObject


并发操作容器

使用并发容器的单个实例,其中,
APIObject
的每次修改都被放入线程安全容器中,并通过遍历关键部分之外的容器并调用所有操作,在上面的任务中显式执行。(不是使用者模式,因为等待容器的新条目不能阻止任务,因为关键部分必须定期执行)

这带来了一些缺点。捕获上下文时的关闭问题可能是其中之一。另一种方法是从
APIObject
读取数据,只要容器中的操作没有执行,就会返回旧数据。更糟糕的是,如果将
APIObject
的创建放在容器中,随后的代码假设已经创建了它


用等待句柄和原子增量来弥补

可以使用
ManualResetEvent
保护每个
APIObject
访问。关键部分将等待信号由
APIObjects
设置,仅当对
APIObjects
的所有调用完成时(访问
APIObjects
时的某种原子增量/减量)才会设置信号

听起来是解决死锁的好方法。当连续
APIObject
调用阻止信号设置时,可能会长时间锁定关键部分

无法解决在关键段期间可能无法访问
APIObjects
的问题,因为此构造仅在另一个方向进行防护。 需要额外的锁定(例如,在关键部分的
Monitor.IsEntered
,以不锁定对不同
APIObjects的同时调用)


=>糟糕的方法是,如果复制一个
APIObject
相对便宜(或者如果它相当昂贵并且你不经常同步),那么你可以把对象放在一个包含单个
全局时间戳和
局部时间戳的包装器中。更新对象时,首先检查是否
global\u timestamp==long.MaxValue
:如果为true,则返回一个破坏性更新的对象;如果
global\u时间戳!=long.MaxValue
global\u timestamp==local\u timestamp
,然后返回一个破坏性更新的对象。但是如果
global\u时间戳!=long.MaxValue
全局时间戳!=然后返回对象的更新副本,并设置
local\u timestamp=global\u timestamp
。执行同步时,请使用
联锁
更新来设置
global\u timestamp=DateTime.UtcNow.ToBinary
,同步完成时设置
global\u timestamp=long.MaxValue
。这样,在执行同步时,程序的其余部分就不必暂停,并且同步应该具有一致的数据

// APIObject provided to you
public class APIObject {
  private string foo;
  public void setFoo(string _foo) {
    this.foo = _foo;
  }
}

// Global Timestamp, readonly version for wrappers and readwrite version for sync
public class GlobalTimestamp {
  protected long timestamp = long.MaxValue;

  public long getTimestamp() {
    return timestamp;
  }
}

public class GlobalTimestampRW extends GlobalTimestamp {
  public void startSync(long _timestamp) {
    long value = System.Threading.Interlocked.CompareExchange(ref timestamp, _timestamp, long.MaxValue);
    if(value != long.MaxValue) throw exception; // somebody else called this method already
  }

  public void endSync(long _timestamp) {
    long value = System.Threading.Interlocked.CompareExchange(ref timestamp, long.MaxValue, _timestamp);
    if(value != _timestamp) throw exception; // somebody else called this method already
  }
}

// Wrapper
public class APIWrapper {
  private APIObject apiObject;
  private GlobalTimestamp globalTimestamp;
  private long localTimestamp = long.MinValue;

  public APIObject setFoo(string _foo) {
    long tempGlobalTimestamp = globalTimestamp.getTimestamp();
    if(tempGlobalTimestamp == long.MaxValue || tempGlobalTimestamp == localTimestamp) {
      apiObject.setFoo(_foo);
      return apiObject;
    } else {
      apiObject = apiObject.copy();
      apiObject.setFoo(_foo);
      localTimestamp = tempGlobalTimestamp;
      return apiObject;
    }
  }
}

GlobalTimestampRW globalTimestamp;
new Task(() =>
{
    while(true)
    {
      long timestamp = DateTime.UtcNow.ToBinary();
      globalTimestamp.startSync(timestamp);
      API.CriticalOperationStart(); // Between start and end no API Object may be used
      API.CriticalOperationEnd();
      globalTimestamp.endSync(timestamp);
    }
}, TaskCreationOptions.LongRunning).Start();

如果复制一个
APIObject
相对便宜(或者如果它相当昂贵,并且您不经常同步),那么您可以将对象放入一个包装器中,该包装器包含一个单例
global\u时间戳
和一个
local\u时间戳
。更新对象时,首先检查是否
global\u timestamp==long.MaxValue
:如果为true,则返回一个破坏性更新的对象;如果
global\u时间戳!=long.MaxValue
global\u timestamp==local\u timestamp
,然后返回一个破坏性更新的对象。但是如果
global\u时间戳!=long.MaxValue
全局时间戳!=然后返回对象的更新副本,并设置
local\u timestamp=global\u timestamp
。执行同步时,请使用
联锁
更新来设置
global\u timestamp=DateTime.UtcNow.ToBinary
,同步完成时设置
global\u timestamp=long.MaxValue
。这样,在执行同步时,程序的其余部分就不必暂停,并且同步应该具有一致的数据

// APIObject provided to you
public class APIObject {
  private string foo;
  public void setFoo(string _foo) {
    this.foo = _foo;
  }
}

// Global Timestamp, readonly version for wrappers and readwrite version for sync
public class GlobalTimestamp {
  protected long timestamp = long.MaxValue;

  public long getTimestamp() {
    return timestamp;
  }
}

public class GlobalTimestampRW extends GlobalTimestamp {
  public void startSync(long _timestamp) {
    long value = System.Threading.Interlocked.CompareExchange(ref timestamp, _timestamp, long.MaxValue);
    if(value != long.MaxValue) throw exception; // somebody else called this method already
  }

  public void endSync(long _timestamp) {
    long value = System.Threading.Interlocked.CompareExchange(ref timestamp, long.MaxValue, _timestamp);
    if(value != _timestamp) throw exception; // somebody else called this method already
  }
}

// Wrapper
public class APIWrapper {
  private APIObject apiObject;
  private GlobalTimestamp globalTimestamp;
  private long localTimestamp = long.MinValue;

  public APIObject setFoo(string _foo) {
    long tempGlobalTimestamp = globalTimestamp.getTimestamp();
    if(tempGlobalTimestamp == long.MaxValue || tempGlobalTimestamp == localTimestamp) {
      apiObject.setFoo(_foo);
      return apiObject;
    } else {
      apiObject = apiObject.copy();
      apiObject.setFoo(_foo);
      localTimestamp = tempGlobalTimestamp;
      return apiObject;
    }
  }
}

GlobalTimestampRW globalTimestamp;
new Task(() =>
{
    while(true)
    {
      long timestamp = DateTime.UtcNow.ToBinary();
      globalTimestamp.startSync(timestamp);
      API.CriticalOperationStart(); // Between start and end no API Object may be used
      API.CriticalOperationEnd();
      globalTimestamp.endSync(timestamp);
    }
}, TaskCreationOptions.LongRunning).Start();

我突然想到,您可能会为此滥用读写器锁。每个线程都会请求一个读锁,而这个读锁不会锁定其他同样需要读锁的线程