C# Xamarin Forms - 执行任务并设置超时

3

像其他人一样,我需要编写一个返回任务的函数,并希望该任务在一定时间后自动超时。

初始代码如下:

class MyClass
{
    TaskCompletionSource<string> m_source;

    public Task<string> GetDataFromServer()
    {
        m_source = new TaskCompletionSource<string> ();

        // System call I have no visibility into, and that doesn't inherently take any
        // sort of timeout or cancellation token
        ask_server_for_data_and_when_youve_got_it_call(Callback);

        return m_source.Task;
    }

    protected void Callback(string data);
    {
        // Got the data!
        m_source.TrySetResult(data);
    }
}

现在我希望它能更聪明一些,在适当的时候自动超时。我有几个选项来实现这个目标:

class MyClass
{
    TaskCompletionSource<string> m_source;

    public Task<string> GetDataFromServer(int timeoutInSeconds)
    {
        m_source = new TaskCompletionSource<string> ();

        ask_server_for_data_and_when_youve_got_it_call(Callback);

        // Method #1 to set up the timeout:
        CancellationToken ct = new CancellationToken ();
        CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource (ct);
        cts.CancelAfter (timeoutInSeconds * 1000);
        cts.Token.Register(() => m_source.TrySetCancelled());

        // Method #2 to set up the timeout:
        CancellationTokenSource ct2 = new CancellationTokenSource ();
        ct2.CancelAfter (timeoutInSeconds * 1000);
        ct2.Token.Register (() => m_source.TrySetCancelled());

        // Method #3 to set up the timeout:
        System.Threading.Tasks.Task.Factory.StartNew (async () =>
        {
            await System.Threading.Tasks.Task.Delay (timeoutInSeconds * 1000);
            m_source.TrySetCancelled();
        });

        // Method #4 to set up the timeout:
        Xamarin.Forms.Device.StartTimer (new TimeSpan (0, 0, timeoutInSeconds),
            () => m_source.TrySetCancelled());

        return m_source.Task;
    }

    protected void Callback(string data);
    {
        // Got the data!
        m_source.TrySetResult(data);
    }
}

四种设置超时的方法各有利弊。例如,我猜想第二种方法最轻量级(需要最少的系统资源)?

还有其他我可能遗漏的设置超时的方法吗?

p.s.

一个我曾经吃过亏的知识点是 - 如果你从主UI线程以外的线程调用GetDataFromServer()方法:

Task.Run(() => await GetDataFromServer());    

在iOS上,第四种方法(Xamarin.Forms.Device.StartTimer)从不触发。

第一个函数是异步的,因此它应该返回字符串,而不是任务。如果您希望此函数返回任务本身,请删除 async 关键字。 - Peter Bruins
我已经添加了“await”函数,这就是为什么我有“async”关键字的原因。 - Betty Crokker
但这甚至无法编译...看起来你正在混合使用同步和异步代码。首先,您向服务器发送异步请求,然后是带有回调的同步请求。一个带有 public async Task<string>...... 的函数应该返回一个字符串。 - Peter Bruins
你说得对,这不是我的实际代码,只是示例代码,以帮助解释我的问题。我已经更新了代码,删除了“async”。 - Betty Crokker
1个回答

4

我认为使用 Task.DelayTask.WhenAny 会更容易:

public async Task<string> GetDataFromServerAsync(int timeoutInSeconds)
{
  Task<string> requestTask = GetDataFromServerAsync();
  var timeoutTask = Task.Delay(timeoutInSeconds);
  var completedTask = await Task.WhenAny(requestTask, timeoutTask);
  if (completedTask == timeoutTask)
    throw new OperationCanceledException();
  return await requestTask;
}

其他方法的缺点:

方法 #1: 没有理由创建新的CancellationToken。这只是方法#2的低效版本。

方法 #2: 通常情况下,当任务完成后,您应该处置Register的结果。在这种情况下,由于CTS最终总是被取消,它可能会正常工作。

方法 #3: 只是为了调用Delay而使用StartNew - 不确定其原因。这基本上是DelayWhenAny的低效版本。

方法 #4: 可以接受。虽然你必须处理TaskCompletionSource<T>及其怪癖(例如默认情况下同步继续)。


现在我们有5种方法来实现超时!我仍然很好奇每种方法的优缺点,为什么在某些情况下你会选择其中一种而不是另一种... - Betty Crokker
@BettyCrokker:带有观点的编辑。 - Stephen Cleary
为什么你返回的是 "await requestTask" 而不是仅仅返回 "requestTask"? - Betty Crokker
@BettyCrokker:那个不会编译。 - Stephen Cleary
好的。抱歉,我对async/await还比较新,仍在适应语法的奇特之处。 - Betty Crokker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接