Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/20.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
.net 线程安全异步不可重入任务_.net_Multithreading_Asynchronous_Task Parallel Library - Fatal编程技术网

.net 线程安全异步不可重入任务

.net 线程安全异步不可重入任务,.net,multithreading,asynchronous,task-parallel-library,.net,Multithreading,Asynchronous,Task Parallel Library,如何构造异步任务,使其一次最多运行一个任务实例?如果在前一个实例运行时调用该任务一次或多次,则前一个实例应完成,然后该任务应再运行一次 任务调用可以来自任何线程。任务没有参数,也没有结果;调用方法签名如下:Task DoItAsync() 这种按需、不可重入任务的用例包括执行后台索引和服务器同步。这是一个包装器,用于保存要运行的操作,并根据需要负责运行该操作,以便在完整的运行完成后通知调用方 /// <summary> /// Runs an asynchronous action

如何构造异步任务,使其一次最多运行一个任务实例?如果在前一个实例运行时调用该任务一次或多次,则前一个实例应完成,然后该任务应再运行一次

任务调用可以来自任何线程。任务没有参数,也没有结果;调用方法签名如下:
Task DoItAsync()


这种按需、不可重入任务的用例包括执行后台索引和服务器同步。

这是一个包装器,用于保存要运行的操作,并根据需要负责运行该操作,以便在完整的运行完成后通知调用方

/// <summary>
/// Runs an asynchronous action such that at most one instance of the action runs at a time.
/// If the action is invoked one or more times while a previous instance is running,
/// the previous instance completes, and then the action runs one additional time.
/// </summary>
public class RepeatableActionRunner
{
    enum RunState { NotRunning, RunningOnce, RunningAndWillRunAgain };

    readonly Func<Task> action;
    RunState runState;
    Task currentTask = Task.CompletedTask;
    Task nextTask = Task.CompletedTask;
    readonly object lockObject = new object();

    public RepeatableActionRunner(Func<Task> action)
    {
        this.action = action;
    }

    /// <summary>
    /// Runs the action and returns a task that completes when the action completes.
    /// </summary>
    /// <remarks>This method is thread safe.</remarks>
    public Task RunAsync()
    {
        lock (lockObject) {
            switch (runState) {
                case RunState.NotRunning:
                    return StartTaskAsync();
                case RunState.RunningAndWillRunAgain:
                    return nextTask;
                default:
                    runState = RunState.RunningAndWillRunAgain;
                    return nextTask = currentTask.ContinueWith(_ => {
                        lock (lockObject)
                            return StartTaskAsync();
                    }).Unwrap();
            }
        }
    }

    Task StartTaskAsync()
    {
        runState = RunState.RunningOnce;
        return currentTask = action().ContinueWith(_ => {
            lock (lockObject)
                runState = runState - 1;
        });
    }
}
//
///运行异步操作,以便一次最多运行一个操作实例。
///如果在前一个实例运行时调用该操作一次或多次,
///前一个实例完成,然后该操作再运行一次。
/// 
公共类RepeatableActionRunner
{
枚举运行状态{NotRunning,RunningOnce,runningandwillrunreach};
只读函数操作;
运行状态;
Task currentTask=Task.CompletedTask;
Task nextTask=Task.CompletedTask;
只读对象lockObject=新对象();
公共RepeatableActionRunner(函数操作)
{
这个动作=动作;
}
/// 
///运行操作并返回在操作完成时完成的任务。
/// 
///此方法是线程安全的。
公共任务RunAsync()
{
锁定(锁定对象){
开关(运行状态){
案例RunState.NotRunning:
返回StartTaskAsync();
case RunState.runningandwillrunnear:
返回下一个任务;
违约:
runState=runState.runningandwillrunreach;
return nextTask=currentTask.ContinueWith(=>{
锁定(锁定对象)
返回StartTaskAsync();
}).Unwrap();
}
}
}
任务StartTaskAsync()
{
runState=runState.RunningOnce;
return currentTask=action(){
锁定(锁定对象)
运行状态=运行状态-1;
});
}
}

这是一个经过调整的版本,它使用信号量等待,因此如果我们确实需要等待锁释放,我们将异步等待

readonly SemaphoreSlim _someSemaphore = new SemaphoreSlim(1);
Task _currentTask = Task.CompletedTask;
Task _nextTask = Task.CompletedTask;

public async Task DoItAsync()
{
    Task taskToAwait;
    await _someSemaphore.WaitAsync();
    try
    {
        if (!_nextTask.IsCompleted)
        {
            taskToAwait =  _nextTask;
        }
        else if(_currentTask.IsCompleted)
        {
            taskToAwait = _currentTask = DoItNowAsync(null);
        }
        else
        {
            taskToAwait = _nextTask = _currentTask.ContinueWith(DoItNowAsync).Unwrap();
        }
    }
    finally
    {
        _someSemaphore.Release();
    }

    await taskToAwait;
}

async Task DoItNowAsync(Task _)
{
    // Do the work, including async operations.
}
该类已经允许您向块发布请求,并让它使用指定的DOP异步执行这些请求。默认DOP为1

就其本身而言,这将确保一次只执行一次,后续请求将排队。要根据计划请求执行,可以使用计时器将请求发布到块

例如:

//Block field with gratuitous timestamp
ActionBlock<DateTime> _rebuildBlock;
_rebuildBlock=new ActionBlock<DateTime>(async dt=>await RebuildIndex(dt));

//From any thread:

_rebuildBlock.Post(DateTime.Now);
您可以创建一个类来抽象块或多个块,例如:

class MyProcessor
{
    ActionBlock<DateTime> _rebuildBlock;

    MyProcessor()
    {
        _rebuildBlock=new ActionBlock<DateTime>(async dt=>await RebuildIndex(dt));
    }

    public void Rebuild()
    {
        _rebuildBlock.Post(DateTime.Now);
    }

    private async Task  RebuildIndex(DateTime timestamp)
    {
       //...
    }

    public Task StopAsync()
    {
        _rebuildBlock.Complete();
        return _rebuilcBlock.Completion;
    }
}
类MyProcessor
{
动作块_重建块;
MyProcessor()
{
_rebuildBlock=newactionblock(异步dt=>await-RebuildIndex(dt));
}
公共空间重建()
{
_重建block.Post(DateTime.Now);
}
专用异步任务重建索引(日期时间时间戳)
{
//...
}
公共任务StopAsync()
{
_rebuildBlock.Complete();
返回_rebuildcblock.Completion;
}
}
ActionBlock可以链接到TPL数据流命名空间中的其他块,以创建处理步骤管道,类似于Powershell或SSIS管道

例如,执行CSV文件批量导入的管道可能如下所示:

//Create the blocks
var folderBlock=new TransformManyBlock<string,string>(folder=>Directory.EnumerateFiles(folder));
var csvBlock=new TransformBlock<string,DataRow>(filePath=>ParseCsv(filePath));
var batchBlock=new BatchBlock<DataRow>(1000);
var dbBlock=new ActionBlock<DataRow[]>(rows=>RunSqlBulkCopy(rows));

//Link them
var options=new DataflowLinkOptions{PropagateCompletion=true};
folderBlock.LinkTo(csvBlock,options);
csvBlock.LinkTo(batchBlock,options);
batchBlock.LinkTo(dbBlock,options);

//Process 100 folders
foreach(var path in aLotOfFolders)
{
    folderBlock.Post(path);
}

//Finished with the folders
folderBlock.Complete();
//Wait for the entire pipeline to complete
await dbBlock.Completion;
//创建块
var folderBlock=newtransformmanyblock(folder=>Directory.EnumerateFiles(folder));
var csvBlock=newtransformblock(filePath=>ParseCsv(filePath));
var batchBlock=新的batchBlock(1000);
var dbBlock=newactionblock(行=>RunSqlBulkCopy(行));
//联系他们
var options=newdataflowlinkoptions{PropagateCompletion=true};
LinkTo(csvBlock,选项);
csvBlock.LinkTo(批块,选项);
LinkTo(dbBlock,选项);
//处理100个文件夹
foreach(文件夹中的变量路径)
{
folderBlock.Post(路径);
}
//文件夹已完成
folderBlock.Complete();
//等待整个管道完成
等待dbBlock完成;
如果希望一次只对一个请求排队,可以创建一个管道,其中包含一个和一个队列长度为1的ActionBlock:

var execOptions = new ExecutionDataflowBlockOptions{BoundedCapacity=1};

var rebuildBlock=new ActionBlock<DateTime>(async dt=>await RebuildIndex(dt),execOptions);
var broadcast=new BroadcastBlock<DateTime>(msg=>msg);    

var options=new DataflowLinkOptions{PropagateCompletion=true};
broadcast.LinkTo(rebuildBlock,options);
var execOptions=new ExecutionDataflowBlockOptions{BoundedCapacity=1};
var-rebuilddblock=newactionblock(异步dt=>await-RebuildIndex(dt),execOptions);
var广播=新广播块(msg=>msg);
var options=newdataflowlinkoptions{PropagateCompletion=true};
broadcast.LinkTo(重建块,选项);

此后,在执行
Rebuild
时发布到广播块的任何内容都将覆盖以前的任何请求

我将使用线程并简单地中止(通过发送事件[!])一个正在运行的线程并启动一个新线程,或者在可能的情况下向我的线程发送事件,以通知他再次执行所有操作。在这种情况下,支持增量更新。因此,与其中止,不如运行到完成,然后再次运行以获取任何新的更改。这也应该可以很好地工作。您只需向线程发出“重启”的信号。首先完成当前运行,然后线程开始下一次(增量)运行。ActionBlock一次包含一个队列调用。计时器可以定期向块发送消息以触发exeuction@PanagiotisKanavos使用基于计时器的轮询方法,您必须等待计时器启动,并等待资源(包括手机上的电池)每次计时器不必要地触发时都会浪费。唯一要注意的是,如果
DoItNowAsync
在第一次
wait
之前有一长段不可等待的代码,则可以长时间保持锁。
var execOptions = new ExecutionDataflowBlockOptions{BoundedCapacity=1};

var rebuildBlock=new ActionBlock<DateTime>(async dt=>await RebuildIndex(dt),execOptions);
var broadcast=new BroadcastBlock<DateTime>(msg=>msg);    

var options=new DataflowLinkOptions{PropagateCompletion=true};
broadcast.LinkTo(rebuildBlock,options);