C#基于任务的异步模式开发教程

任务一般是一些比较耗时的操作(IO或者复杂计算),如果在主线程运行,将影响程序的流畅性。所以,我们一般会新建线程处理任务。.NET4开始引进了Task,它对Thread做了大量方便易用的封装。我们将详细讲述Task的使用方法,以及各种多线程的使用场合。

一、任务设计

1.1 基本设计

一个简单的耗时操作如下所示:

void Func1()
{
    Thread.Sleep(1000);
}

如果有返回值,将如下所示:

int Func2()
{
    Thread.Sleep(1500);
    return 1;
}

耗时操作如果要新建线程去运行,可以这么写:

Task task1 = new Task(Func1);
task1.Start();

Task task2 = new Task(Func2);
task2.Start();

C#5.0引入了async、await这些关键字,异步方法的写法为:

async Task Func1()
{
    await Task.Delay(1000);
}

async Task Func2()
{
    await Task.Delay(1500);
    return 1;
}

调用时就像同步的方法一样:

Func1();
Func2();

需要注意的是,虽然代码看上去是先执行Func1,然后再执行Func2,但实际上,这两个都是异步方法,它们会同时开始执行。

 

1.2 可取消的任务

在使用Thread时会看到一个Abort方法,用来终止线程。但其实这个方法非常不安全,甚至不能终止,一般不提倡使用。事实上,如果一个耗时方法是我们自己设计的,我们可以想办法在中途退出;而如果这个方法我们只能调用,无法修改,那这个任务基本无法中止。

假设有一个耗时方法如下所示:

async Task Func1()
{
    await Task.Delay(10000);
}

一旦调用,这个方法将运行10秒。为了避免这种情况,我们需要对等待进行切割。如:

async Task Func1()
{
    for (int i = 0; i < 100; i++)
    {
        await Task.Delay(100);
    }
}

如果希望中途能够取消,那么可以写成:

async Task Func1(CancellationToken cancellationToken)
{
    for (int i = 0; i < 100; i++)
    {
        if (cancellationToken.IsCancellationRequested)
        {
            return;
        }
        await Task.Delay(100);
    }
}

调用的方法如下:

CancellationTokenSource cts = new CancellationTokenSource();
await Func1(cts.Token); 

如果需要在另一个线程里面取消,只需要调用以下语句即可:

cts.Cancel();

 

1.3 进度报告

有时候我们需要实时知道耗时任务的运行进度,例如下载,可能需要向用户提供一个完成百分比。

带进度报告功能的异步任务设计如下:

async Task Func1(IProgress progress)
{
    for (int i = 0; i < 100; i++)
    {
        await Task.Delay(100);
        progress.Report(i + 1);
    }
}

调用方法则如下所示:

await Func1(new Progress(p =>
{
    Console.WriteLine(p);
}));

 

二、单任务使用

对于没有返回值的任务,我们上面已经提到了使用的方法。如果任务有返回值,而我们需要使用这个返回值,调用的方法如下:

(1)使用async、await关键字

async Task Func2()
{
    await Task.Delay(1500);
    return 1;
}


//调用方法
int result = await Func2();

(2)不使用async、await关键字

int Func2()
{
    Thread.Sleep(1500);
    return 1;
}


//调用方法
Task task = new Task(Func2);
task.ContinueWith(t =>
{
    int result = t.Result;
});

 

三、多任务使用

3.1 所有任务完成后处理

使用WhenAll方法,使用方法如下:

async Task Func1()
{
    await Task.Delay(1000);
    return 1;
}

async Task Func2()
{
    await Task.Delay(1500);
    return 2;
}



Task[] tasks = new Task[] { Func1(), Func2() };
int[] results = await Task.WhenAll(tasks);

3.2 任一任务完成即处理

使用WhenAny方法,使用方法如下:

async Task Func1()
{
    await Task.Delay(1000);
    return 1;
}

async Task Func2()
{
    await Task.Delay(1500);
    return 2;
}



Task[] tasks = new Task[] { Func1(), Func2() };
Task task = await Task.WhenAny(tasks);
int result = task.Result;

WhenAny方法可以有以下的应用场景:

(1)超时判断。写一个简单的延时任务,跟需要执行的任务一同放在列表里。如果WhenAny先返回延时任务,说明需要执行的任务已经超时了。

(2)一个任务完成,取消执行余下任务。结合CancellationToken完成这一功能,具体代码如下:

async Task Func1(CancellationToken cancellationToken)
{
    for (int i = 0; i < 20; i++)
    {
        if (cancellationToken.IsCancellationRequested)
        {
            return;
        }

        await Task.Delay(100);
    }
}

async Task Func2(CancellationToken cancellationToken)
{
    for (int i = 0; i < 100; i++)
    {
        if (cancellationToken.IsCancellationRequested)
        {
            return;
        }

        await Task.Delay(110);
    }
}




CancellationTokenSource cts = new CancellationTokenSource();
Task[] tasks = new Task[] { Func1(cts.Token), Func2(cts.Token) };
Task task = await Task.WhenAny(tasks);
cts.Cancel();

 

你可能感兴趣的:(多线程)