Skip to content

Instantly share code, notes, and snippets.

@GuilhermeMatheus
Created March 15, 2018 13:32
Show Gist options
  • Select an option

  • Save GuilhermeMatheus/8a26fecf3286ffba4819ecb7bc737249 to your computer and use it in GitHub Desktop.

Select an option

Save GuilhermeMatheus/8a26fecf3286ffba4819ecb7bc737249 to your computer and use it in GitHub Desktop.
Usando PauseTokenSource e PauseToken para notificar pausas em Tasks.
using System;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// Classe de teste do PauseTokenSource e PauseToken
/// </summary>
class Program
{
static void Main()
{
var pts = new PauseTokenSource();
Task.Run(() =>
{
while (true)
{
Console.WriteLine(
pts.IsPaused
? "Task pausada. Dê enter para continuar sua execução..."
: "Task executando. Dê enter para pausar sua execução...");
Console.ReadLine();
pts.IsPaused = !pts.IsPaused;
}
});
MetodoAssincronoDeExemploAsync(pts.Token).Wait();
}
public static async Task MetodoAssincronoDeExemploAsync(PauseToken pause)
{
for (int i = 1; i < Int32.MaxValue; i++)
{
Console.WriteLine($"Sou uma Task executando um loop pela {i}º vez . . .");
await Task.Delay(1000);
await pause.WaitWhilePausedAsync();
}
}
}
/// <summary>
/// A PauseTokenSource é a classe geradora de tokens de pausa, o PauseToken.
/// A separação destas classes é importante para impedir que um usuário de PauseToken
/// altere o valor de sua propriedade IsPaused enquanto o criador do token de pausa
/// poderá sinalizar a pausa através do PauseTokenSource
/// </summary>
public class PauseTokenSource
{
// O modificador VOLATILE indica ao compilador que o campo
// deve sempre ser operado na memória principal do computador.
// Sem este modificador, é possível que este campo seja operado
// na memória CACHE de um núcleo da CPU. Sendo assim, dois nucleos
// operando simultaneamente neste campo podem ver valores diferentes.
// Veremos que o setter de IsPaused se preocupa com isto.
private volatile TaskCompletionSource<bool> m_paused;
internal static readonly Task s_completedTask = Task.FromResult(true);
// O token que indica se a tarefa deve ser pausada ou não!
public PauseToken Token { get { return new PauseToken(this); } }
// Nesta propriedade, podemos pausar ou continuar a execução
// das tasks clientes do Token PauseToken
public bool IsPaused
{
get { return m_paused != null; }
set
{
if (value)
{
// A classe Interlocked é usada para operações atômicas.
// Ou seja, o campo m_paused será acessado apenas por uma
// thread por vez e não simultaneamente - o que pode causar um erro.
// Para pausar, a lógica é definir a TaskCompletionSource m_paused para
// um novo new TaskCompletionSource<bool>()
Interlocked.CompareExchange(
ref m_paused, new TaskCompletionSource<bool>(), null);
}
else
{
while (true)
{
var tcs = m_paused;
if (tcs == null) return;
// Para continuar, a lógica é definir a m_paused para nulo
if (Interlocked.CompareExchange(ref m_paused, null, tcs) == tcs)
{
tcs.SetResult(true);
break;
}
}
}
}
}
// O modificador internal é para o PauseToken ter visibilidade
// deste método enquanto ele será invisível para outros projetos
internal Task WaitWhilePausedAsync()
{
var cur = m_paused;
return cur != null ? cur.Task : s_completedTask;
}
}
/// <summary>
/// O token de pausa é representado em uma Struct pois seus campos são
/// todos de tipo de valor, é imutável, não é usado em operações de box/unboxing
/// e não gera pressão no Garbage Collector.
/// </summary>
public struct PauseToken
{
private readonly PauseTokenSource m_source;
internal PauseToken(PauseTokenSource source) { m_source = source; }
// Apenas retorna a propriedade IsPaused de PauseTokenSource
public bool IsPaused { get { return m_source != null && m_source.IsPaused; } }
// Método auxiliar para retornar uma Task para podermos dar await enquanto
// a aplicação está pausada
public Task WaitWhilePausedAsync()
{
return IsPaused ?
m_source.WaitWhilePausedAsync() :
PauseTokenSource.s_completedTask;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment